@hawkeyexl/manni 2.2.0 → 2.3.1
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/README.md +1 -1
- package/dist/{chunk-6JWX6JM2.js → chunk-CD3LHG26.js} +174 -83
- package/dist/chunk-CD3LHG26.js.map +1 -0
- package/dist/{chunk-2Q5JUEIO.js → chunk-ESOEJTV2.js} +4 -11
- package/dist/{chunk-2Q5JUEIO.js.map → chunk-ESOEJTV2.js.map} +1 -1
- package/dist/{chunk-5O4FIGDJ.js → chunk-HUHERW7E.js} +183 -128
- package/dist/chunk-HUHERW7E.js.map +1 -0
- package/dist/cli.js +4082 -78
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +2 -2
- package/dist/index.d.ts +30 -25
- package/dist/index.js +2 -2
- package/package.json +2 -1
- package/dist/chunk-5O4FIGDJ.js.map +0 -1
- package/dist/chunk-6JWX6JM2.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,15 @@ import {
|
|
|
7
7
|
readStdin,
|
|
8
8
|
reportConfig,
|
|
9
9
|
splitList
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ESOEJTV2.js";
|
|
11
11
|
import {
|
|
12
12
|
CiteError,
|
|
13
13
|
KeyError,
|
|
14
14
|
SEVERITIES,
|
|
15
15
|
SEVERITY_LIST,
|
|
16
16
|
asCiteError,
|
|
17
|
+
codeEndAt,
|
|
18
|
+
codeRegions,
|
|
17
19
|
isSeverity,
|
|
18
20
|
meetsSeverity,
|
|
19
21
|
renderCheckGithub,
|
|
@@ -30,35 +32,49 @@ import {
|
|
|
30
32
|
shortPin,
|
|
31
33
|
shortSrc,
|
|
32
34
|
spellSource
|
|
33
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-CD3LHG26.js";
|
|
34
36
|
import {
|
|
35
37
|
COMMON_FORMAT_LIST,
|
|
36
38
|
OMITTED_WHEN_CLEAN,
|
|
37
39
|
REPORT_FORMATS,
|
|
38
40
|
REPORT_FORMAT_LIST,
|
|
41
|
+
STDIN_LABEL,
|
|
39
42
|
STDIN_TOKEN,
|
|
40
43
|
ToolError,
|
|
44
|
+
assertNonEmpty,
|
|
45
|
+
errorMessage,
|
|
41
46
|
escapeWorkflowCommandMessage,
|
|
42
47
|
escapeWorkflowCommandProperty,
|
|
48
|
+
extractorByName,
|
|
49
|
+
extractorForExtension,
|
|
43
50
|
fail,
|
|
44
51
|
findConfigFile,
|
|
52
|
+
gitignoreOptions,
|
|
53
|
+
isErrorSeverity,
|
|
45
54
|
isMachineFormat,
|
|
46
55
|
isReportFormat,
|
|
56
|
+
locateFrontmatter,
|
|
47
57
|
notice,
|
|
48
58
|
package_default,
|
|
49
59
|
palette,
|
|
50
60
|
programName,
|
|
51
61
|
readConfigFile,
|
|
52
62
|
render,
|
|
63
|
+
renderJunit,
|
|
64
|
+
renderSarif,
|
|
65
|
+
resolveBaselineRequest,
|
|
66
|
+
resolveTargetSet,
|
|
53
67
|
runIfMain,
|
|
54
68
|
selectCollections,
|
|
69
|
+
settleBaseline,
|
|
55
70
|
shouldColor,
|
|
56
71
|
splitPageArgument,
|
|
57
|
-
terminalConfirm
|
|
58
|
-
|
|
72
|
+
terminalConfirm,
|
|
73
|
+
valeConfigPath
|
|
74
|
+
} from "./chunk-HUHERW7E.js";
|
|
59
75
|
|
|
60
76
|
// src/cli.ts
|
|
61
|
-
import { Command as
|
|
77
|
+
import { Command as Command5 } from "commander";
|
|
62
78
|
|
|
63
79
|
// src/a11y/cli.ts
|
|
64
80
|
import { Command } from "commander";
|
|
@@ -204,9 +220,9 @@ async function crawl(opts, analyzer) {
|
|
|
204
220
|
progress({ kind: "checked", index, url, violations: 0, error });
|
|
205
221
|
}
|
|
206
222
|
}
|
|
207
|
-
const
|
|
208
|
-
progress({ kind: "done", checked: pages.length, skipped });
|
|
209
|
-
return { pages, discovered: frontier.length, skipped, duplicates };
|
|
223
|
+
const skipped2 = frontier.length - pages.length - duplicates;
|
|
224
|
+
progress({ kind: "done", checked: pages.length, skipped: skipped2 });
|
|
225
|
+
return { pages, discovered: frontier.length, skipped: skipped2, duplicates };
|
|
210
226
|
}
|
|
211
227
|
|
|
212
228
|
// src/a11y/core/seeds.ts
|
|
@@ -273,11 +289,11 @@ async function discoverSitemap(seed, fetcher = defaultFetcher) {
|
|
|
273
289
|
}
|
|
274
290
|
return { source: null, urls: [] };
|
|
275
291
|
}
|
|
276
|
-
function parseSitemapXml(
|
|
277
|
-
if (
|
|
292
|
+
function parseSitemapXml(text2) {
|
|
293
|
+
if (text2.trim() === "") return null;
|
|
278
294
|
let root;
|
|
279
295
|
try {
|
|
280
|
-
const doc = new DOMParser({ onError: () => void 0 }).parseFromString(
|
|
296
|
+
const doc = new DOMParser({ onError: () => void 0 }).parseFromString(text2, "text/xml");
|
|
281
297
|
root = doc.documentElement;
|
|
282
298
|
} catch {
|
|
283
299
|
return null;
|
|
@@ -335,10 +351,10 @@ function ancestorDirectories(pathname) {
|
|
|
335
351
|
}
|
|
336
352
|
}
|
|
337
353
|
async function robotsSitemaps(url, fetcher) {
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
354
|
+
const text2 = await fetchText(url, fetcher);
|
|
355
|
+
if (text2 === null) return [];
|
|
340
356
|
const out = [];
|
|
341
|
-
for (const line of
|
|
357
|
+
for (const line of text2.split(/\r?\n/)) {
|
|
342
358
|
const match = /^\s*sitemap\s*:\s*(\S+)/i.exec(line);
|
|
343
359
|
const candidate = match?.[1];
|
|
344
360
|
if (candidate !== void 0 && isHttpUrl(candidate) && !out.includes(candidate)) {
|
|
@@ -348,8 +364,8 @@ async function robotsSitemaps(url, fetcher) {
|
|
|
348
364
|
return out;
|
|
349
365
|
}
|
|
350
366
|
async function fetchSitemap(url, fetcher) {
|
|
351
|
-
const
|
|
352
|
-
return
|
|
367
|
+
const text2 = await fetchText(url, fetcher);
|
|
368
|
+
return text2 === null ? null : parseSitemapXml(text2);
|
|
353
369
|
}
|
|
354
370
|
async function fetchText(url, fetcher) {
|
|
355
371
|
try {
|
|
@@ -423,7 +439,7 @@ function finishPage(page, floor) {
|
|
|
423
439
|
const score = applicable === 0 ? null : Math.round(100 * page.passes / applicable);
|
|
424
440
|
return { ...page, violations, score };
|
|
425
441
|
}
|
|
426
|
-
function summarize(results, { discovered, skipped, duplicates }, sitemap, crawl2) {
|
|
442
|
+
function summarize(results, { discovered, skipped: skipped2, duplicates }, sitemap, crawl2) {
|
|
427
443
|
const bySeverity = { notice: 0, warning: 0, error: 0 };
|
|
428
444
|
let failed = 0;
|
|
429
445
|
let violations = 0;
|
|
@@ -437,7 +453,7 @@ function summarize(results, { discovered, skipped, duplicates }, sitemap, crawl2
|
|
|
437
453
|
return {
|
|
438
454
|
discovered,
|
|
439
455
|
checked: results.length,
|
|
440
|
-
skipped,
|
|
456
|
+
skipped: skipped2,
|
|
441
457
|
duplicates,
|
|
442
458
|
failed,
|
|
443
459
|
violations,
|
|
@@ -706,10 +722,10 @@ function scoreText(page) {
|
|
|
706
722
|
return page.score === null ? "n/a" : String(page.score);
|
|
707
723
|
}
|
|
708
724
|
function severityCounts(violations, c) {
|
|
709
|
-
const
|
|
710
|
-
for (const v of violations)
|
|
711
|
-
return [...SEVERITIES].reverse().filter((severity) =>
|
|
712
|
-
const n =
|
|
725
|
+
const counts2 = { notice: 0, warning: 0, error: 0 };
|
|
726
|
+
for (const v of violations) counts2[v.severity] += 1;
|
|
727
|
+
return [...SEVERITIES].reverse().filter((severity) => counts2[severity] > 0).map((severity) => {
|
|
728
|
+
const n = counts2[severity];
|
|
713
729
|
return paintSeverity(`${n} ${severity}${n === 1 ? "" : "s"}`, severity, c);
|
|
714
730
|
}).join(", ");
|
|
715
731
|
}
|
|
@@ -729,14 +745,14 @@ function oneLine(summary) {
|
|
|
729
745
|
if (head === void 0) return "";
|
|
730
746
|
return rest.length === 0 ? head : `${head} ${rest.join("; ")}`;
|
|
731
747
|
}
|
|
732
|
-
function paintSeverity(
|
|
748
|
+
function paintSeverity(text2, severity, c) {
|
|
733
749
|
switch (severity) {
|
|
734
750
|
case "notice":
|
|
735
|
-
return c.dim(
|
|
751
|
+
return c.dim(text2);
|
|
736
752
|
case "warning":
|
|
737
|
-
return c.yellow(
|
|
753
|
+
return c.yellow(text2);
|
|
738
754
|
case "error":
|
|
739
|
-
return c.red(
|
|
755
|
+
return c.red(text2);
|
|
740
756
|
}
|
|
741
757
|
}
|
|
742
758
|
|
|
@@ -779,8 +795,8 @@ function terminalReporter(opts) {
|
|
|
779
795
|
const { stream } = opts;
|
|
780
796
|
const c = palette(opts.color);
|
|
781
797
|
let queued = 0;
|
|
782
|
-
const show = (
|
|
783
|
-
stream.write(`${CLEAR_LINE}${
|
|
798
|
+
const show = (text2) => {
|
|
799
|
+
stream.write(`${CLEAR_LINE}${text2}`);
|
|
784
800
|
};
|
|
785
801
|
const showUrl = (prefix, url, suffix = "") => {
|
|
786
802
|
const columns = stream.columns ?? DEFAULT_COLUMNS;
|
|
@@ -816,8 +832,8 @@ function outcome(event, c) {
|
|
|
816
832
|
}
|
|
817
833
|
function plainReporter(stream) {
|
|
818
834
|
let queued = 0;
|
|
819
|
-
const say = (
|
|
820
|
-
stream.write(`${programName()}: ${
|
|
835
|
+
const say = (text2) => {
|
|
836
|
+
stream.write(`${programName()}: ${text2}
|
|
821
837
|
`);
|
|
822
838
|
};
|
|
823
839
|
return (event) => {
|
|
@@ -846,15 +862,15 @@ function plainReporter(stream) {
|
|
|
846
862
|
}
|
|
847
863
|
};
|
|
848
864
|
}
|
|
849
|
-
function visibleLength(
|
|
850
|
-
return
|
|
865
|
+
function visibleLength(text2) {
|
|
866
|
+
return text2.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
851
867
|
}
|
|
852
|
-
function truncateMiddle(
|
|
868
|
+
function truncateMiddle(text2, room) {
|
|
853
869
|
const max = Math.max(room, 8);
|
|
854
|
-
if (
|
|
870
|
+
if (text2.length <= max) return text2;
|
|
855
871
|
const head = Math.ceil((max - 1) / 2);
|
|
856
872
|
const tail = max - 1 - head;
|
|
857
|
-
return `${
|
|
873
|
+
return `${text2.slice(0, head)}\u2026${text2.slice(text2.length - tail)}`;
|
|
858
874
|
}
|
|
859
875
|
|
|
860
876
|
// src/a11y/cli.ts
|
|
@@ -955,9 +971,9 @@ function buildProgram2() {
|
|
|
955
971
|
{ analyzer: createPlaywrightAnalyzer(), onProgress: progress.listener }
|
|
956
972
|
);
|
|
957
973
|
const color = resolveColor(parent, process.stdout);
|
|
958
|
-
const
|
|
959
|
-
if (
|
|
960
|
-
process.stdout.write(`${
|
|
974
|
+
const text2 = render2(format, run, { color, quiet: Boolean(options.quiet) });
|
|
975
|
+
if (text2.length > 0 || format !== "github") {
|
|
976
|
+
process.stdout.write(`${text2}
|
|
961
977
|
`);
|
|
962
978
|
}
|
|
963
979
|
process.exitCode = run.summary.failed > 0 ? 1 : 0;
|
|
@@ -1002,7 +1018,7 @@ function addMessage(result) {
|
|
|
1002
1018
|
const where2 = result.manifest === void 0 ? "frontmatter" : `${result.manifest.file}:${String(result.manifest.line)}`;
|
|
1003
1019
|
const head = `${result.file}: added ${name} to ${where2}`;
|
|
1004
1020
|
if (result.markerLine !== void 0) {
|
|
1005
|
-
const at = claimLines === void 0 ? "" : `, claim pinned at
|
|
1021
|
+
const at = claimLines === void 0 ? "" : `, claim pinned at ${spellAt(claimLines)}`;
|
|
1006
1022
|
return `${head}; marker at line ${String(result.markerLine)}${at}`;
|
|
1007
1023
|
}
|
|
1008
1024
|
if (claim === void 0 || claimLines === void 0) {
|
|
@@ -1087,10 +1103,10 @@ function buildProgram3() {
|
|
|
1087
1103
|
showDiff: options.showDiff ? true : void 0,
|
|
1088
1104
|
root: options.root
|
|
1089
1105
|
});
|
|
1090
|
-
let
|
|
1106
|
+
let text2;
|
|
1091
1107
|
switch (format) {
|
|
1092
1108
|
case "pretty":
|
|
1093
|
-
|
|
1109
|
+
text2 = renderCheckPretty(run, {
|
|
1094
1110
|
color: colorFor(command, process.stdout.isTTY),
|
|
1095
1111
|
quiet: Boolean(options.quiet),
|
|
1096
1112
|
showDiff: Boolean(options.showDiff),
|
|
@@ -1098,20 +1114,20 @@ function buildProgram3() {
|
|
|
1098
1114
|
});
|
|
1099
1115
|
break;
|
|
1100
1116
|
case "json":
|
|
1101
|
-
|
|
1117
|
+
text2 = renderCheckJson(run);
|
|
1102
1118
|
break;
|
|
1103
1119
|
case "github":
|
|
1104
|
-
|
|
1120
|
+
text2 = renderCheckGithub(run);
|
|
1105
1121
|
break;
|
|
1106
1122
|
default:
|
|
1107
|
-
|
|
1123
|
+
text2 = render(format, run.results, run.summary, {
|
|
1108
1124
|
frame: run.frame,
|
|
1109
1125
|
classname: JUNIT_CLASSNAME,
|
|
1110
1126
|
onNotice: notice
|
|
1111
1127
|
});
|
|
1112
1128
|
}
|
|
1113
|
-
if (
|
|
1114
|
-
process.stdout.write(`${
|
|
1129
|
+
if (text2.length > 0 || !OMITTED_WHEN_CLEAN.has(format)) {
|
|
1130
|
+
process.stdout.write(`${text2}
|
|
1115
1131
|
`);
|
|
1116
1132
|
}
|
|
1117
1133
|
process.exitCode = run.summary.failed > 0 ? 1 : 0;
|
|
@@ -1125,7 +1141,7 @@ function buildProgram3() {
|
|
|
1125
1141
|
).argument(
|
|
1126
1142
|
"<src>",
|
|
1127
1143
|
"path, path:L or path:L1-L2 relative to --root, or an encrypted ~source with the same line forms"
|
|
1128
|
-
).option("--id <id>", "kebab-case id, unique on the page; required with --marker").option("--marker", "write a cite <id> marker above the page lines and pin the text it anchors").option("--quote", "the page lines are a fenced block that reproduces the source").option(
|
|
1144
|
+
).option("--id <id>", "kebab-case id, unique on the page; required with --marker").option("--marker", "write a cite <id> marker above the paragraph holding the page lines and pin the text it anchors").option("--quote", "the page lines are a fenced block that reproduces the source").option(
|
|
1129
1145
|
"--encrypt",
|
|
1130
1146
|
"write source.file encrypted, with an hmac-sha256- pin; with no key, offer to create one (an available key encrypts without the flag)"
|
|
1131
1147
|
).option("--no-commit-sha", "do not record HEAD").option("--dry-run", "print the diff; write nothing").option("--as <format>", "force the page format").option("--root <dir>", "directory source paths resolve from (as check)").option("-c, --config <path>", "path to a manni config file").option("--no-config", "ignore any discovered config file").addHelpText(
|
|
@@ -1249,7 +1265,7 @@ function buildProgram3() {
|
|
|
1249
1265
|
only: options.only.length > 0 ? options.only : void 0,
|
|
1250
1266
|
dryRun
|
|
1251
1267
|
});
|
|
1252
|
-
const
|
|
1268
|
+
const text2 = format === "json" ? renderUpdateJson(run) : renderUpdatePretty(run, {
|
|
1253
1269
|
color: colorFor(command, process.stdout.isTTY),
|
|
1254
1270
|
// The diffs are the point of a dry run; a real run prints
|
|
1255
1271
|
// what it rewrote and leaves the file to say the rest.
|
|
@@ -1258,10 +1274,10 @@ function buildProgram3() {
|
|
|
1258
1274
|
const stdinPage = pageToStdout ? run.pages.find((page) => page.content !== void 0) : void 0;
|
|
1259
1275
|
if (stdinPage?.content !== void 0) {
|
|
1260
1276
|
process.stdout.write(stdinPage.content);
|
|
1261
|
-
process.stderr.write(`${
|
|
1277
|
+
process.stderr.write(`${text2}
|
|
1262
1278
|
`);
|
|
1263
1279
|
} else {
|
|
1264
|
-
process.stdout.write(`${
|
|
1280
|
+
process.stdout.write(`${text2}
|
|
1265
1281
|
`);
|
|
1266
1282
|
}
|
|
1267
1283
|
process.exitCode = run.exitCode;
|
|
@@ -1454,44 +1470,4032 @@ function buildProgram4() {
|
|
|
1454
1470
|
return program;
|
|
1455
1471
|
}
|
|
1456
1472
|
|
|
1457
|
-
// src/cli.ts
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
+
// src/term/cli.ts
|
|
1474
|
+
import { Command as Command4 } from "commander";
|
|
1475
|
+
|
|
1476
|
+
// src/term/commands/check.ts
|
|
1477
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1478
|
+
|
|
1479
|
+
// src/term/core/config.ts
|
|
1480
|
+
import { resolve } from "node:path";
|
|
1481
|
+
|
|
1482
|
+
// src/term/errors.ts
|
|
1483
|
+
var TermError = class extends ToolError {
|
|
1484
|
+
constructor(message) {
|
|
1485
|
+
super(message);
|
|
1486
|
+
this.name = "TermError";
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
// src/term/types.ts
|
|
1491
|
+
var TERM_FIELDS = [
|
|
1492
|
+
"label",
|
|
1493
|
+
"definition",
|
|
1494
|
+
"abstract",
|
|
1495
|
+
"alt-labels",
|
|
1496
|
+
"hidden-labels",
|
|
1497
|
+
"broader",
|
|
1498
|
+
"narrower",
|
|
1499
|
+
"related-terms",
|
|
1500
|
+
"see",
|
|
1501
|
+
"scope-note"
|
|
1502
|
+
];
|
|
1503
|
+
var TERM_LIST_FIELDS = [
|
|
1504
|
+
"alt-labels",
|
|
1505
|
+
"hidden-labels",
|
|
1506
|
+
"broader",
|
|
1507
|
+
"narrower",
|
|
1508
|
+
"related-terms"
|
|
1509
|
+
];
|
|
1510
|
+
var TERM_WRITE_FORMATS = [
|
|
1511
|
+
"markdown",
|
|
1512
|
+
"mdx",
|
|
1513
|
+
"asciidoc",
|
|
1514
|
+
"rst",
|
|
1515
|
+
"html",
|
|
1516
|
+
"dita",
|
|
1517
|
+
"docbook",
|
|
1518
|
+
"tbx",
|
|
1519
|
+
"skos",
|
|
1520
|
+
"csv",
|
|
1521
|
+
"json",
|
|
1522
|
+
"vale"
|
|
1523
|
+
];
|
|
1524
|
+
var TERM_RULES = [
|
|
1525
|
+
"undefined-term",
|
|
1526
|
+
"duplicate-id",
|
|
1527
|
+
"label-collision",
|
|
1528
|
+
"alt-label-collision",
|
|
1529
|
+
"dangling-reference",
|
|
1530
|
+
"broader-cycle",
|
|
1531
|
+
"see-not-empty",
|
|
1532
|
+
"asymmetric-hierarchy",
|
|
1533
|
+
"abstract-too-long",
|
|
1534
|
+
"unused-term"
|
|
1535
|
+
];
|
|
1536
|
+
|
|
1537
|
+
// src/term/core/config.ts
|
|
1538
|
+
var TERM_SECTION = "term";
|
|
1539
|
+
var DEFAULT_TERM_BASELINE_PATH = ".manni-term-baseline.json";
|
|
1540
|
+
var DEFAULT_ABSTRACT_MAX_LENGTH = 60;
|
|
1541
|
+
var CONFIG_KEYS2 = [
|
|
1542
|
+
"manifests",
|
|
1543
|
+
"abstractMaxLength",
|
|
1544
|
+
"baseline",
|
|
1545
|
+
"severity",
|
|
1546
|
+
"allowEmpty",
|
|
1547
|
+
"respectGitignore"
|
|
1548
|
+
];
|
|
1549
|
+
var COLLECTION_KEYS = ["paths", "exclude"];
|
|
1550
|
+
var TOOL_KEYS = ["vale"];
|
|
1551
|
+
var SEVERITY_LEVELS = [...SEVERITIES, "off"];
|
|
1552
|
+
var CONFIG_FILE2 = {
|
|
1553
|
+
section: TERM_SECTION,
|
|
1554
|
+
legacyNames: [],
|
|
1555
|
+
toError: (message) => new TermError(message)
|
|
1556
|
+
};
|
|
1557
|
+
function isMapping(value) {
|
|
1558
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1559
|
+
}
|
|
1560
|
+
function isLevel(value) {
|
|
1561
|
+
return typeof value === "string" && SEVERITY_LEVELS.includes(value);
|
|
1562
|
+
}
|
|
1563
|
+
function isTermRule(value) {
|
|
1564
|
+
return TERM_RULES.includes(value);
|
|
1565
|
+
}
|
|
1566
|
+
function rejectUnknownKeys(obj, allowed, where2, source) {
|
|
1567
|
+
for (const key of Object.keys(obj)) {
|
|
1568
|
+
if (!allowed.includes(key)) {
|
|
1569
|
+
throw new TermError(
|
|
1570
|
+
`Unknown key "${key}" under ${where2}: in ${source}. Supported keys: ${allowed.join(", ")}.`
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function wrongType(key, expected, source) {
|
|
1576
|
+
return new TermError(`term.${key} in ${source} must be ${expected}.`);
|
|
1577
|
+
}
|
|
1578
|
+
function asBoolean2(value, key, source) {
|
|
1579
|
+
if (typeof value !== "boolean") throw wrongType(key, "a boolean", source);
|
|
1580
|
+
return value;
|
|
1581
|
+
}
|
|
1582
|
+
function asString(value, key, source) {
|
|
1583
|
+
if (typeof value !== "string") throw wrongType(key, "a string", source);
|
|
1584
|
+
return value;
|
|
1585
|
+
}
|
|
1586
|
+
function asStringList2(value, key, source) {
|
|
1587
|
+
if (!Array.isArray(value) || !value.every((item2) => typeof item2 === "string" && item2.trim() !== "")) {
|
|
1588
|
+
throw wrongType(key, "a list of non-empty strings", source);
|
|
1589
|
+
}
|
|
1590
|
+
return value;
|
|
1591
|
+
}
|
|
1592
|
+
function asNonNegativeInteger(value, key, source) {
|
|
1593
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
1594
|
+
throw wrongType(key, "a non-negative integer", source);
|
|
1595
|
+
}
|
|
1596
|
+
return value;
|
|
1597
|
+
}
|
|
1598
|
+
function parseSeverity(value, source) {
|
|
1599
|
+
if (!isMapping(value)) throw wrongType("severity", "a mapping", source);
|
|
1600
|
+
rejectUnknownKeys(value, TERM_RULES, "term.severity", source);
|
|
1601
|
+
const severity = {};
|
|
1602
|
+
for (const [rule, level] of Object.entries(value)) {
|
|
1603
|
+
if (!isTermRule(rule)) continue;
|
|
1604
|
+
if (!isLevel(level)) {
|
|
1605
|
+
const quoted = typeof level === "string" ? ` "${level}"` : "";
|
|
1606
|
+
throw new TermError(
|
|
1607
|
+
`${source}: term.severity.${rule}${quoted} is not a level. Expected ${SEVERITY_LEVELS.join(" | ")}.`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
severity[rule] = level;
|
|
1611
|
+
}
|
|
1612
|
+
return severity;
|
|
1613
|
+
}
|
|
1614
|
+
function parseTermConfig(raw, source) {
|
|
1615
|
+
if (raw == null) return {};
|
|
1616
|
+
if (!isMapping(raw)) {
|
|
1617
|
+
throw new TermError(`term: in ${source} must be a mapping.`);
|
|
1618
|
+
}
|
|
1619
|
+
for (const key of COLLECTION_KEYS) {
|
|
1620
|
+
if (Object.hasOwn(raw, key)) {
|
|
1621
|
+
throw new TermError(
|
|
1622
|
+
`${source}: term does not carry "${key}". Name a collection under collections:.`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
for (const key of TOOL_KEYS) {
|
|
1627
|
+
if (Object.hasOwn(raw, key)) {
|
|
1628
|
+
throw new TermError(
|
|
1629
|
+
`${source}: term does not carry "${key}". Vale's settings live under tools.${key}.`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
rejectUnknownKeys(raw, CONFIG_KEYS2, "term", source);
|
|
1634
|
+
const config = {};
|
|
1635
|
+
if (raw.manifests !== void 0) config.manifests = asStringList2(raw.manifests, "manifests", source);
|
|
1636
|
+
if (raw.abstractMaxLength !== void 0) {
|
|
1637
|
+
config.abstractMaxLength = asNonNegativeInteger(raw.abstractMaxLength, "abstractMaxLength", source);
|
|
1638
|
+
}
|
|
1639
|
+
if (raw.baseline !== void 0) config.baseline = asString(raw.baseline, "baseline", source);
|
|
1640
|
+
if (raw.severity !== void 0) config.severity = parseSeverity(raw.severity, source);
|
|
1641
|
+
if (raw.allowEmpty !== void 0) config.allowEmpty = asBoolean2(raw.allowEmpty, "allowEmpty", source);
|
|
1642
|
+
if (raw.respectGitignore !== void 0) {
|
|
1643
|
+
config.respectGitignore = asBoolean2(raw.respectGitignore, "respectGitignore", source);
|
|
1644
|
+
}
|
|
1645
|
+
return config;
|
|
1646
|
+
}
|
|
1647
|
+
function readTermConfigFile(explicitPath, cwd) {
|
|
1648
|
+
return explicitPath ? readConfigFile(explicitPath, cwd, CONFIG_FILE2) : findConfigFile(cwd, CONFIG_FILE2);
|
|
1649
|
+
}
|
|
1650
|
+
function toLoaded(file) {
|
|
1651
|
+
return {
|
|
1652
|
+
config: parseTermConfig(file.value, file.source),
|
|
1653
|
+
path: file.path,
|
|
1654
|
+
dir: file.dir,
|
|
1655
|
+
source: file.source,
|
|
1656
|
+
collections: file.collections,
|
|
1657
|
+
tools: file.tools
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
var STDIN = "-";
|
|
1661
|
+
async function resolveTermRun(opts) {
|
|
1662
|
+
const cwd = resolve(opts.cwd);
|
|
1663
|
+
const wanted = opts.collection ?? [];
|
|
1664
|
+
if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN)) {
|
|
1665
|
+
throw new TermError(
|
|
1666
|
+
"--collection selects a configured collection; it cannot be combined with paths."
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
const file = opts.noConfig ? null : await readTermConfigFile(opts.configPath, cwd);
|
|
1670
|
+
const loaded = file === null ? null : toLoaded(file);
|
|
1671
|
+
if (wanted.length > 0 && loaded === null) {
|
|
1672
|
+
throw new TermError("--collection needs a config file to select from.");
|
|
1673
|
+
}
|
|
1674
|
+
if (loaded) opts.onConfigLoaded?.({ path: loaded.path, dir: loaded.dir });
|
|
1675
|
+
const collections = loaded ? selectCollections(loaded.collections, wanted, loaded.source, (message) => new TermError(message)) : [];
|
|
1676
|
+
const fromCollections = opts.inputs.length === 0;
|
|
1677
|
+
const inputs = fromCollections ? collections.flatMap((c) => c.paths) : opts.inputs;
|
|
1678
|
+
const base = fromCollections && inputs.length > 0 && loaded ? loaded.dir : cwd;
|
|
1679
|
+
const manifests = loaded ? (loaded.config.manifests ?? []).map((written) => ({ path: resolve(loaded.dir, written), written })) : [];
|
|
1680
|
+
return {
|
|
1681
|
+
config: loaded ? loaded.config : null,
|
|
1682
|
+
inputs,
|
|
1683
|
+
base,
|
|
1684
|
+
collections,
|
|
1685
|
+
fromCollections,
|
|
1686
|
+
manifests,
|
|
1687
|
+
tools: loaded ? loaded.tools : {},
|
|
1688
|
+
...loaded ? { configDir: loaded.dir, configPath: loaded.path, configSource: loaded.source } : {},
|
|
1689
|
+
...file === null ? {} : { configFile: file }
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/term/core/severity.ts
|
|
1694
|
+
var RULE_ID_PREFIX = "manni:term";
|
|
1695
|
+
var DEFAULT_SEVERITY = {
|
|
1696
|
+
"undefined-term": "error",
|
|
1697
|
+
"duplicate-id": "error",
|
|
1698
|
+
"label-collision": "error",
|
|
1699
|
+
"alt-label-collision": "error",
|
|
1700
|
+
"dangling-reference": "error",
|
|
1701
|
+
"broader-cycle": "error",
|
|
1702
|
+
"see-not-empty": "error",
|
|
1703
|
+
"asymmetric-hierarchy": "warning",
|
|
1704
|
+
"abstract-too-long": "notice",
|
|
1705
|
+
"unused-term": "notice"
|
|
1706
|
+
};
|
|
1707
|
+
function ruleId(rule) {
|
|
1708
|
+
return `${RULE_ID_PREFIX}/${rule}`;
|
|
1709
|
+
}
|
|
1710
|
+
function proseRuleId(check) {
|
|
1711
|
+
return `${RULE_ID_PREFIX}/prose/${check}`;
|
|
1712
|
+
}
|
|
1713
|
+
function isTermRule2(value) {
|
|
1714
|
+
return TERM_RULES.includes(value);
|
|
1715
|
+
}
|
|
1716
|
+
function resolveSeverity(overrides) {
|
|
1717
|
+
return { ...DEFAULT_SEVERITY, ...overrides };
|
|
1718
|
+
}
|
|
1719
|
+
function foldValeSeverity(severity) {
|
|
1720
|
+
return severity === "suggestion" ? "notice" : severity;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
// src/term/core/check.ts
|
|
1724
|
+
var REFERENCE_FIELDS = ["broader", "narrower", "related-terms", "see"];
|
|
1725
|
+
function checkTermSet(set, opts = {}) {
|
|
1726
|
+
const levels = resolveSeverity(opts.severity);
|
|
1727
|
+
const maxLength = opts.abstractMaxLength ?? DEFAULT_ABSTRACT_MAX_LENGTH;
|
|
1728
|
+
const { terms } = set;
|
|
1729
|
+
const drafts = [];
|
|
1730
|
+
const fold = (value) => value.toLowerCase();
|
|
1731
|
+
const where2 = (t) => `${t.location.file}:${String(t.location.line)}`;
|
|
1732
|
+
const onEntry = (t, rule, message, field) => ({
|
|
1733
|
+
rule,
|
|
1734
|
+
message,
|
|
1735
|
+
file: t.location.file,
|
|
1736
|
+
line: (field === void 0 ? void 0 : t.location.fieldLines[field]) ?? t.location.line,
|
|
1737
|
+
id: t.id,
|
|
1738
|
+
...field === void 0 ? {} : { field }
|
|
1739
|
+
});
|
|
1740
|
+
const byLabel = /* @__PURE__ */ new Map();
|
|
1741
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1742
|
+
terms.forEach((t, index) => {
|
|
1743
|
+
const label = fold(t.record.label);
|
|
1744
|
+
if (!byLabel.has(label)) byLabel.set(label, index);
|
|
1745
|
+
const id = fold(t.id);
|
|
1746
|
+
if (!byId.has(id)) byId.set(id, index);
|
|
1747
|
+
});
|
|
1748
|
+
const resolveReference = (value) => byLabel.get(fold(value)) ?? byId.get(fold(value));
|
|
1749
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1750
|
+
for (const reference of set.references) {
|
|
1751
|
+
const label = fold(reference.label);
|
|
1752
|
+
referenced.add(label);
|
|
1753
|
+
if (byLabel.has(label)) continue;
|
|
1754
|
+
let message = `concepts: "${reference.label}" names no entry.`;
|
|
1755
|
+
const synonym = terms.find((t) => (t.record["alt-labels"] ?? []).some((alt) => fold(alt) === label));
|
|
1756
|
+
if (synonym !== void 0) {
|
|
1757
|
+
message += ` "${synonym.record.label}" lists it as an alt-label.`;
|
|
1758
|
+
}
|
|
1759
|
+
drafts.push({
|
|
1760
|
+
rule: "undefined-term",
|
|
1761
|
+
message,
|
|
1762
|
+
file: reference.file,
|
|
1763
|
+
...reference.line === void 0 ? {} : { line: reference.line }
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
const sharing = /* @__PURE__ */ new Map();
|
|
1767
|
+
for (const t of terms) {
|
|
1768
|
+
const group = sharing.get(t.id);
|
|
1769
|
+
if (group === void 0) sharing.set(t.id, [t]);
|
|
1770
|
+
else group.push(t);
|
|
1771
|
+
}
|
|
1772
|
+
for (const group of sharing.values()) {
|
|
1773
|
+
if (group.length < 2) continue;
|
|
1774
|
+
for (const t of group) {
|
|
1775
|
+
const others = group.filter((other) => other !== t).map(where2);
|
|
1776
|
+
drafts.push(onEntry(t, "duplicate-id", `id: "${t.id}" is also used by ${others.join(", ")}`));
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
terms.forEach((t, index) => {
|
|
1780
|
+
const first = byLabel.get(fold(t.record.label));
|
|
1781
|
+
const firstTerm = first === void 0 ? void 0 : terms[first];
|
|
1782
|
+
if (first !== void 0 && first !== index && firstTerm !== void 0) {
|
|
1783
|
+
drafts.push(
|
|
1784
|
+
onEntry(
|
|
1785
|
+
t,
|
|
1786
|
+
"label-collision",
|
|
1787
|
+
`"${t.record.label}" is claimed by ${where2(firstTerm)} as "${firstTerm.record.label}"`,
|
|
1788
|
+
"label"
|
|
1789
|
+
)
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
for (const alt of t.record["alt-labels"] ?? []) {
|
|
1793
|
+
const owner = terms.find((other, otherIndex) => otherIndex !== index && fold(other.record.label) === fold(alt));
|
|
1794
|
+
if (owner !== void 0) {
|
|
1795
|
+
drafts.push(
|
|
1796
|
+
onEntry(t, "alt-label-collision", `alt-labels: "${alt}" is the label of ${where2(owner)}`, "alt-labels")
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
for (const field of REFERENCE_FIELDS) {
|
|
1801
|
+
for (const value of referenceValues(t, field)) {
|
|
1802
|
+
if (resolveReference(value) === void 0) {
|
|
1803
|
+
drafts.push(onEntry(t, "dangling-reference", `${field}: "${value}" names no entry`, field));
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
if (t.record.see !== void 0 && t.record.definition !== void 0) {
|
|
1808
|
+
drafts.push(
|
|
1809
|
+
onEntry(t, "see-not-empty", `see: "${t.record.see}" redirects this entry, so remove its definition`, "see")
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
const abstract = t.record.abstract;
|
|
1813
|
+
if (abstract !== void 0 && abstract.length > maxLength) {
|
|
1814
|
+
drafts.push(
|
|
1815
|
+
onEntry(
|
|
1816
|
+
t,
|
|
1817
|
+
"abstract-too-long",
|
|
1818
|
+
`abstract: ${String(abstract.length)} characters is over the limit of ${String(maxLength)}, so shorten it`,
|
|
1819
|
+
"abstract"
|
|
1820
|
+
)
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
if (!referenced.has(fold(t.record.label))) {
|
|
1824
|
+
drafts.push(onEntry(t, "unused-term", "no page's concepts: names this term"));
|
|
1825
|
+
}
|
|
1826
|
+
});
|
|
1827
|
+
const edges = (field) => terms.map((t) => {
|
|
1828
|
+
const targets = /* @__PURE__ */ new Set();
|
|
1829
|
+
for (const value of referenceValues(t, field)) {
|
|
1830
|
+
const target = resolveReference(value);
|
|
1831
|
+
if (target !== void 0) targets.add(target);
|
|
1832
|
+
}
|
|
1833
|
+
return targets;
|
|
1834
|
+
});
|
|
1835
|
+
const broader = edges("broader");
|
|
1836
|
+
const narrower = edges("narrower");
|
|
1837
|
+
terms.forEach((t, index) => {
|
|
1838
|
+
for (const parent of broader[index] ?? []) {
|
|
1839
|
+
const parentTerm = terms[parent];
|
|
1840
|
+
if (parentTerm !== void 0 && !(narrower[parent]?.has(index) ?? false)) {
|
|
1841
|
+
drafts.push(
|
|
1842
|
+
onEntry(
|
|
1843
|
+
parentTerm,
|
|
1844
|
+
"asymmetric-hierarchy",
|
|
1845
|
+
`narrower: omits "${t.record.label}", which lists this entry as broader`,
|
|
1846
|
+
"narrower"
|
|
1847
|
+
)
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
for (const child of narrower[index] ?? []) {
|
|
1852
|
+
const childTerm = terms[child];
|
|
1853
|
+
if (childTerm !== void 0 && !(broader[child]?.has(index) ?? false)) {
|
|
1854
|
+
drafts.push(
|
|
1855
|
+
onEntry(
|
|
1856
|
+
childTerm,
|
|
1857
|
+
"asymmetric-hierarchy",
|
|
1858
|
+
`broader: omits "${t.record.label}", which lists this entry as narrower`,
|
|
1859
|
+
"broader"
|
|
1860
|
+
)
|
|
1473
1861
|
);
|
|
1474
1862
|
}
|
|
1475
1863
|
}
|
|
1476
1864
|
});
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
)
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
)
|
|
1865
|
+
for (const [start2, path] of broaderCycles(broader)) {
|
|
1866
|
+
const startTerm = terms[start2];
|
|
1867
|
+
if (startTerm === void 0) continue;
|
|
1868
|
+
const labels = [...path, start2].map((index) => terms[index]?.record.label ?? "");
|
|
1869
|
+
drafts.push(onEntry(startTerm, "broader-cycle", `broader: ${labels.join(" > ")}`, "broader"));
|
|
1870
|
+
}
|
|
1871
|
+
const findings = [];
|
|
1872
|
+
for (const draft of drafts) {
|
|
1873
|
+
const level = levels[draft.rule];
|
|
1874
|
+
if (level === "off") continue;
|
|
1875
|
+
findings.push({
|
|
1876
|
+
rule: draft.rule,
|
|
1877
|
+
ruleId: ruleId(draft.rule),
|
|
1878
|
+
severity: level,
|
|
1879
|
+
message: draft.message,
|
|
1880
|
+
file: draft.file,
|
|
1881
|
+
...draft.line === void 0 ? {} : { line: draft.line },
|
|
1882
|
+
...draft.id === void 0 ? {} : { id: draft.id },
|
|
1883
|
+
...draft.field === void 0 ? {} : { field: draft.field }
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
return findings.sort(compareFindings);
|
|
1887
|
+
}
|
|
1888
|
+
function referenceValues(t, field) {
|
|
1889
|
+
const value = t.record[field];
|
|
1890
|
+
if (value === void 0) return [];
|
|
1891
|
+
return typeof value === "string" ? [value] : value;
|
|
1892
|
+
}
|
|
1893
|
+
function broaderCycles(broader) {
|
|
1894
|
+
const cycles = [];
|
|
1895
|
+
for (let start2 = 0; start2 < broader.length; start2++) {
|
|
1896
|
+
const path = [start2];
|
|
1897
|
+
const onPath = /* @__PURE__ */ new Set([start2]);
|
|
1898
|
+
const walk = (node) => {
|
|
1899
|
+
for (const next of broader[node] ?? []) {
|
|
1900
|
+
if (next === start2) {
|
|
1901
|
+
cycles.push([start2, [...path]]);
|
|
1902
|
+
} else if (next > start2 && !onPath.has(next)) {
|
|
1903
|
+
path.push(next);
|
|
1904
|
+
onPath.add(next);
|
|
1905
|
+
walk(next);
|
|
1906
|
+
path.pop();
|
|
1907
|
+
onPath.delete(next);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
walk(start2);
|
|
1912
|
+
}
|
|
1913
|
+
return cycles;
|
|
1914
|
+
}
|
|
1915
|
+
function compareText(a, b) {
|
|
1916
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1917
|
+
}
|
|
1918
|
+
function compareFindings(a, b) {
|
|
1919
|
+
return compareText(a.file, b.file) || (a.line ?? 0) - (b.line ?? 0) || compareText(a.ruleId, b.ruleId) || compareText(a.message, b.message);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// src/term/commands/findings.ts
|
|
1923
|
+
var FINDING_FORMATS = ["pretty", "json", "github", "sarif", "junit"];
|
|
1924
|
+
var JUNIT_CLASSNAME2 = "manni.term";
|
|
1925
|
+
var LINE_REFERENCE = /:\d+\b/g;
|
|
1926
|
+
function toFieldError(finding) {
|
|
1927
|
+
const keyword = finding.ruleId.startsWith(`${RULE_ID_PREFIX}/`) ? finding.ruleId.slice(RULE_ID_PREFIX.length + 1) : finding.ruleId;
|
|
1928
|
+
const { field } = finding;
|
|
1929
|
+
const subject = [finding.id ?? "", field ?? "", finding.message.replace(LINE_REFERENCE, "")].join("\0");
|
|
1930
|
+
const error = {
|
|
1931
|
+
schema: RULE_ID_PREFIX,
|
|
1932
|
+
instancePath: field === void 0 ? "" : `/${field}`,
|
|
1933
|
+
message: finding.message,
|
|
1934
|
+
keyword,
|
|
1935
|
+
subject,
|
|
1936
|
+
severity: finding.severity
|
|
1937
|
+
};
|
|
1938
|
+
if (finding.line !== void 0) error.line = finding.line;
|
|
1939
|
+
return error;
|
|
1940
|
+
}
|
|
1941
|
+
function toResults(set, findings) {
|
|
1942
|
+
const origin = /* @__PURE__ */ new Map();
|
|
1943
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1944
|
+
for (const term of set.terms) {
|
|
1945
|
+
if (!byFile.has(term.location.file)) byFile.set(term.location.file, []);
|
|
1946
|
+
}
|
|
1947
|
+
for (const finding of findings) {
|
|
1948
|
+
const error = toFieldError(finding);
|
|
1949
|
+
origin.set(error, finding);
|
|
1950
|
+
const list = byFile.get(finding.file) ?? [];
|
|
1951
|
+
list.push(error);
|
|
1952
|
+
byFile.set(finding.file, list);
|
|
1953
|
+
}
|
|
1954
|
+
const results = [...byFile.keys()].sort().map((file) => {
|
|
1955
|
+
const errors = byFile.get(file) ?? [];
|
|
1956
|
+
return { file, format: "term", ok: !errors.some(isErrorSeverity), schemas: [RULE_ID_PREFIX], errors };
|
|
1957
|
+
});
|
|
1958
|
+
return { results, origin };
|
|
1959
|
+
}
|
|
1960
|
+
function summarize2(results, baseline) {
|
|
1961
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
1962
|
+
const count = (keep) => results.reduce((n, r) => n + r.errors.filter(keep).length, 0);
|
|
1963
|
+
const warnings = count((e) => e.severity === "warning");
|
|
1964
|
+
const notices = count((e) => e.severity === "notice");
|
|
1965
|
+
return {
|
|
1966
|
+
files: results.length,
|
|
1967
|
+
passed: results.length - failed,
|
|
1968
|
+
failed,
|
|
1969
|
+
errors: count(isErrorSeverity),
|
|
1970
|
+
...warnings > 0 ? { warnings } : {},
|
|
1971
|
+
...notices > 0 ? { notices } : {},
|
|
1972
|
+
...baseline ? { baseline } : {}
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
function reportedFindings(results, origin) {
|
|
1976
|
+
const kept = [];
|
|
1977
|
+
for (const result of results) {
|
|
1978
|
+
for (const error of result.errors) {
|
|
1979
|
+
const finding = origin.get(error);
|
|
1980
|
+
if (finding !== void 0) kept.push(finding);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
return kept.sort(compareFindings2);
|
|
1984
|
+
}
|
|
1985
|
+
function compareFindings2(a, b) {
|
|
1986
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
1987
|
+
const lineA = a.line ?? 0;
|
|
1988
|
+
const lineB = b.line ?? 0;
|
|
1989
|
+
if (lineA !== lineB) return lineA - lineB;
|
|
1990
|
+
if (a.ruleId !== b.ruleId) return a.ruleId < b.ruleId ? -1 : 1;
|
|
1991
|
+
return a.message < b.message ? -1 : a.message > b.message ? 1 : 0;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// src/term/commands/run.ts
|
|
1995
|
+
import { existsSync } from "node:fs";
|
|
1996
|
+
import { isAbsolute, relative, resolve as resolve3 } from "node:path";
|
|
1997
|
+
|
|
1998
|
+
// src/term/core/load-set.ts
|
|
1999
|
+
import { readFile } from "node:fs/promises";
|
|
2000
|
+
import { extname, resolve as resolve2 } from "node:path";
|
|
2001
|
+
|
|
2002
|
+
// src/term/core/writers/markup.ts
|
|
2003
|
+
function xmlText(value) {
|
|
2004
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2005
|
+
}
|
|
2006
|
+
function xmlAttribute(value) {
|
|
2007
|
+
return xmlText(value).replaceAll('"', """);
|
|
2008
|
+
}
|
|
2009
|
+
function paragraphsOf(definition) {
|
|
2010
|
+
if (definition === void 0) return [];
|
|
2011
|
+
return definition.split(/\r?\n[ \t]*\r?\n/).map(
|
|
2012
|
+
(paragraph) => paragraph.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "")
|
|
2013
|
+
).filter((lines) => lines.length > 0);
|
|
2014
|
+
}
|
|
2015
|
+
function paragraphText(lines) {
|
|
2016
|
+
return lines.join(" ");
|
|
2017
|
+
}
|
|
2018
|
+
function isAcronym(label) {
|
|
2019
|
+
return new RegExp("\\p{L}", "u").test(label) && !new RegExp("\\p{Ll}", "u").test(label);
|
|
2020
|
+
}
|
|
2021
|
+
function sharedLanguage(terms) {
|
|
2022
|
+
const [first] = terms;
|
|
2023
|
+
const language = first?.language;
|
|
2024
|
+
if (language === void 0) return void 0;
|
|
2025
|
+
return terms.every((t) => t.language === language) ? language : void 0;
|
|
2026
|
+
}
|
|
2027
|
+
function reindent(text2, indent, eol) {
|
|
2028
|
+
return text2.split("\n").map((line, i) => i === 0 || line === "" ? line : `${indent}${line}`).join(eol);
|
|
2029
|
+
}
|
|
2030
|
+
function fileOf(lines) {
|
|
2031
|
+
return lines.map((line) => `${line}
|
|
2032
|
+
`).join("");
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// src/term/core/writers/entries.ts
|
|
2036
|
+
var LIST_HOLDS = ["label", "definition", "alt-labels"];
|
|
2037
|
+
var DITA_HOLDS = ["label", "definition", "alt-labels", "scope-note"];
|
|
2038
|
+
var DOCBOOK_HOLDS = ["label", "definition", "alt-labels", "see", "related-terms"];
|
|
2039
|
+
function labelsOf(record) {
|
|
2040
|
+
return [record.label, ...record["alt-labels"] ?? []];
|
|
2041
|
+
}
|
|
2042
|
+
function markdownEntry(record) {
|
|
2043
|
+
const lines = labelsOf(record);
|
|
2044
|
+
const paragraphs = paragraphsOf(record.definition);
|
|
2045
|
+
if (paragraphs.length === 0) return [...lines, ":"].join("\n");
|
|
2046
|
+
paragraphs.forEach((paragraph, p) => {
|
|
2047
|
+
if (p > 0) lines.push("");
|
|
2048
|
+
paragraph.forEach((line, i) => {
|
|
2049
|
+
lines.push(p === 0 && i === 0 ? `: ${line}` : ` ${line}`);
|
|
2050
|
+
});
|
|
2051
|
+
});
|
|
2052
|
+
return lines.join("\n");
|
|
2053
|
+
}
|
|
2054
|
+
function asciidocEntry(record) {
|
|
2055
|
+
const labels = labelsOf(record);
|
|
2056
|
+
const paragraphs = paragraphsOf(record.definition);
|
|
2057
|
+
const lines = labels.map((label) => `${label}::`);
|
|
2058
|
+
paragraphs.forEach((paragraph, p) => {
|
|
2059
|
+
if (p > 0) lines.push("+");
|
|
2060
|
+
paragraph.forEach((line, i) => {
|
|
2061
|
+
if (p === 0 && i === 0) lines[lines.length - 1] = `${lines[lines.length - 1] ?? ""} ${line}`;
|
|
2062
|
+
else lines.push(line);
|
|
2063
|
+
});
|
|
2064
|
+
});
|
|
2065
|
+
return lines.join("\n");
|
|
2066
|
+
}
|
|
2067
|
+
function rstEntry(record) {
|
|
2068
|
+
const lines = labelsOf(record);
|
|
2069
|
+
paragraphsOf(record.definition).forEach((paragraph, p) => {
|
|
2070
|
+
if (p > 0) lines.push("");
|
|
2071
|
+
for (const line of paragraph) lines.push(` ${line}`);
|
|
2072
|
+
});
|
|
2073
|
+
return lines.join("\n");
|
|
2074
|
+
}
|
|
2075
|
+
function blockDefinition(open, close, paragraph, definition) {
|
|
2076
|
+
const paragraphs = paragraphsOf(definition);
|
|
2077
|
+
const [only] = paragraphs;
|
|
2078
|
+
if (only === void 0) return [];
|
|
2079
|
+
if (paragraphs.length === 1) return [`${open}${xmlText(paragraphText(only))}${close}`];
|
|
2080
|
+
return [
|
|
2081
|
+
open,
|
|
2082
|
+
...paragraphs.map((lines) => ` <${paragraph}>${xmlText(paragraphText(lines))}</${paragraph}>`),
|
|
2083
|
+
close
|
|
2084
|
+
];
|
|
2085
|
+
}
|
|
2086
|
+
function htmlDlEntry(term) {
|
|
2087
|
+
const [label, ...alts] = labelsOf(term.record);
|
|
2088
|
+
return [
|
|
2089
|
+
`<dt id="${xmlAttribute(term.id)}">${xmlText(label ?? "")}</dt>`,
|
|
2090
|
+
...alts.map((alt) => `<dt>${xmlText(alt)}</dt>`),
|
|
2091
|
+
...blockDefinition("<dd>", "</dd>", "p", term.record.definition)
|
|
2092
|
+
].join("\n");
|
|
2093
|
+
}
|
|
2094
|
+
function ditaEntry(term, language) {
|
|
2095
|
+
const { record } = term;
|
|
2096
|
+
const alts = record["alt-labels"] ?? [];
|
|
2097
|
+
const scopeNote = record["scope-note"];
|
|
2098
|
+
const lang = language === void 0 ? "" : ` xml:lang="${xmlAttribute(language)}"`;
|
|
2099
|
+
const body = scopeNote === void 0 && alts.length === 0 ? [] : [
|
|
2100
|
+
" <glossBody>",
|
|
2101
|
+
...scopeNote === void 0 ? [] : [` <glossUsage>${xmlText(scopeNote)}</glossUsage>`],
|
|
2102
|
+
...alts.flatMap((alt) => {
|
|
2103
|
+
const element = isAcronym(alt) ? "glossAcronym" : "glossSynonym";
|
|
2104
|
+
return [" <glossAlt>", ` <${element}>${xmlText(alt)}</${element}>`, " </glossAlt>"];
|
|
2105
|
+
}),
|
|
2106
|
+
" </glossBody>"
|
|
2107
|
+
];
|
|
2108
|
+
return [
|
|
2109
|
+
`<glossentry id="${xmlAttribute(term.id)}"${lang}>`,
|
|
2110
|
+
` <glossterm>${xmlText(record.label)}</glossterm>`,
|
|
2111
|
+
...blockDefinition("<glossdef>", "</glossdef>", "p", record.definition).map((line) => ` ${line}`),
|
|
2112
|
+
...body,
|
|
2113
|
+
"</glossentry>"
|
|
2114
|
+
].join("\n");
|
|
2115
|
+
}
|
|
2116
|
+
function docbookEntry(term) {
|
|
2117
|
+
const { record } = term;
|
|
2118
|
+
const related = record["related-terms"] ?? [];
|
|
2119
|
+
const paragraphs = paragraphsOf(record.definition);
|
|
2120
|
+
const glossdef = paragraphs.length === 0 && related.length === 0 ? [] : [
|
|
2121
|
+
" <glossdef>",
|
|
2122
|
+
...paragraphs.map((lines) => ` <para>${xmlText(paragraphText(lines))}</para>`),
|
|
2123
|
+
...related.map((target) => ` <glossseealso>${xmlText(target)}</glossseealso>`),
|
|
2124
|
+
" </glossdef>"
|
|
2125
|
+
];
|
|
2126
|
+
return [
|
|
2127
|
+
`<glossentry xml:id="${xmlAttribute(term.id)}">`,
|
|
2128
|
+
` <glossterm>${xmlText(record.label)}</glossterm>`,
|
|
2129
|
+
...(record["alt-labels"] ?? []).map(
|
|
2130
|
+
(alt) => isAcronym(alt) ? ` <acronym>${xmlText(alt)}</acronym>` : ` <glossterm>${xmlText(alt)}</glossterm>`
|
|
2131
|
+
),
|
|
2132
|
+
...glossdef,
|
|
2133
|
+
...record.see === void 0 ? [] : [` <glosssee>${xmlText(record.see)}</glosssee>`],
|
|
2134
|
+
"</glossentry>"
|
|
2135
|
+
].join("\n");
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/term/core/id.ts
|
|
2139
|
+
import { slug } from "github-slugger";
|
|
2140
|
+
function slugOf(label) {
|
|
2141
|
+
return slug(label.trim());
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/term/core/readers/normalize.ts
|
|
2145
|
+
function nothing() {
|
|
2146
|
+
return { terms: [], notices: [] };
|
|
2147
|
+
}
|
|
2148
|
+
function textOf(value) {
|
|
2149
|
+
let text2;
|
|
2150
|
+
if (typeof value === "string") text2 = value;
|
|
2151
|
+
else if (typeof value === "number" && Number.isFinite(value)) text2 = String(value);
|
|
2152
|
+
else return void 0;
|
|
2153
|
+
const trimmed = text2.trim();
|
|
2154
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
2155
|
+
}
|
|
2156
|
+
function listOf(value) {
|
|
2157
|
+
const items = Array.isArray(value) ? value : [value];
|
|
2158
|
+
const list = items.map(textOf).filter((item2) => item2 !== void 0);
|
|
2159
|
+
return list.length === 0 ? void 0 : list;
|
|
2160
|
+
}
|
|
2161
|
+
function collapse(text2) {
|
|
2162
|
+
return text2.replace(/\s+/g, " ").trim();
|
|
2163
|
+
}
|
|
2164
|
+
function isListField(field) {
|
|
2165
|
+
return TERM_LIST_FIELDS.includes(field);
|
|
2166
|
+
}
|
|
2167
|
+
function recordOf(raw) {
|
|
2168
|
+
const label = textOf(raw.label);
|
|
2169
|
+
if (label === void 0) return void 0;
|
|
2170
|
+
const record = { label };
|
|
2171
|
+
for (const field of TERM_FIELDS) {
|
|
2172
|
+
if (isListField(field)) {
|
|
2173
|
+
const list = listOf(raw[field]);
|
|
2174
|
+
if (list !== void 0) record[field] = list;
|
|
2175
|
+
} else if (field !== "label") {
|
|
2176
|
+
const text2 = textOf(raw[field]);
|
|
2177
|
+
if (text2 !== void 0) record[field] = text2;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return record;
|
|
2181
|
+
}
|
|
2182
|
+
function ignoredFields(raw) {
|
|
2183
|
+
const ignored = [];
|
|
2184
|
+
for (const field of TERM_FIELDS) {
|
|
2185
|
+
if (field === "label" || isListField(field)) continue;
|
|
2186
|
+
const value = raw[field];
|
|
2187
|
+
if (value === void 0 || value === null || typeof value === "string") continue;
|
|
2188
|
+
if (textOf(value) !== void 0) continue;
|
|
2189
|
+
const given = Array.isArray(value) ? "list" : typeof value === "object" ? "mapping" : "other";
|
|
2190
|
+
ignored.push({ field, given });
|
|
2191
|
+
}
|
|
2192
|
+
return ignored;
|
|
2193
|
+
}
|
|
2194
|
+
function ignoredNotice(input, line, label, ignored) {
|
|
2195
|
+
const { field, given } = ignored;
|
|
2196
|
+
const article = /^[aeiou]/.test(field) ? "an" : "a";
|
|
2197
|
+
const holds = given === "list" ? "holds one value, not a list" : given === "mapping" ? "holds text, not a mapping" : "holds text";
|
|
2198
|
+
return `${input.file}:${line}: ignored ${field} on "${label}": ${article} ${field} ${holds}.`;
|
|
2199
|
+
}
|
|
2200
|
+
function linesFor(record, lines) {
|
|
2201
|
+
const kept = {};
|
|
2202
|
+
for (const field of TERM_FIELDS) {
|
|
2203
|
+
const line = lines[field];
|
|
2204
|
+
if (line !== void 0 && record[field] !== void 0) kept[field] = line;
|
|
2205
|
+
}
|
|
2206
|
+
return kept;
|
|
2207
|
+
}
|
|
2208
|
+
function idFor(record, recordId, constructId) {
|
|
2209
|
+
return textOf(recordId) ?? textOf(constructId) ?? slugOf(record.label);
|
|
2210
|
+
}
|
|
2211
|
+
function languageOf(input) {
|
|
2212
|
+
return textOf(input.metadata["language"]) ?? textOf(input.metadata["xml:lang"]);
|
|
2213
|
+
}
|
|
2214
|
+
function declares(input, type) {
|
|
2215
|
+
const value = input.metadata["type"];
|
|
2216
|
+
return typeof value === "string" && value.trim() === type;
|
|
2217
|
+
}
|
|
2218
|
+
function skipped(input, line, label) {
|
|
2219
|
+
return `${input.file}:${line}: skipped a ${label} entry with no term.`;
|
|
2220
|
+
}
|
|
2221
|
+
function termOf(input, parts) {
|
|
2222
|
+
const language = languageOf(input);
|
|
2223
|
+
const location = {
|
|
2224
|
+
file: input.file,
|
|
2225
|
+
...input.path === void 0 ? {} : { path: input.path },
|
|
2226
|
+
construct: parts.construct,
|
|
2227
|
+
line: parts.line,
|
|
2228
|
+
fieldLines: linesFor(parts.record, parts.lines),
|
|
2229
|
+
...parts.span === void 0 ? {} : { span: parts.span }
|
|
2230
|
+
};
|
|
2231
|
+
return {
|
|
2232
|
+
id: idFor(parts.record, parts.recordId, parts.constructId),
|
|
2233
|
+
record: parts.record,
|
|
2234
|
+
...language === void 0 ? {} : { language },
|
|
2235
|
+
location
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// src/term/core/readers/splice.ts
|
|
2240
|
+
function sameValue(a, b) {
|
|
2241
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
2242
|
+
}
|
|
2243
|
+
function sameFields(a, b, fields) {
|
|
2244
|
+
return fields.every((field) => sameValue(a[field], b[field]));
|
|
2245
|
+
}
|
|
2246
|
+
function lineEnding(content) {
|
|
2247
|
+
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
2248
|
+
}
|
|
2249
|
+
function indentAt(content, offset) {
|
|
2250
|
+
const lineStart = content.lastIndexOf("\n", offset - 1) + 1;
|
|
2251
|
+
return /^[ \t]*/.exec(content.slice(lineStart, offset))?.[0] ?? "";
|
|
2252
|
+
}
|
|
2253
|
+
function applyEntries(input, read8, terms, write) {
|
|
2254
|
+
const { content } = input;
|
|
2255
|
+
const current = read8(input).terms;
|
|
2256
|
+
const eol = lineEnding(content);
|
|
2257
|
+
const edits = [];
|
|
2258
|
+
for (const term of terms) {
|
|
2259
|
+
const span = term.location.span;
|
|
2260
|
+
if (span === void 0) {
|
|
2261
|
+
throw new TermError(
|
|
2262
|
+
`${input.file}: term "${term.id}" cannot be written in place, because its entry has no position in the file.`
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
const found = current.find((t) => t.location.span?.start === span.start && t.location.span.end === span.end);
|
|
2266
|
+
if (found === void 0) {
|
|
2267
|
+
throw new TermError(
|
|
2268
|
+
`${input.file}: term "${term.id}" does not match an entry in the file. Read the file again before writing it.`
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
if (sameFields(found.record, term.record, write.holds) && (!write.id || found.id === term.id)) continue;
|
|
2272
|
+
const text2 = reindent(write.serialize(term, input), indentAt(content, span.start), eol);
|
|
2273
|
+
edits.push({ start: span.start, end: span.end, text: text2 });
|
|
2274
|
+
}
|
|
2275
|
+
let next = content;
|
|
2276
|
+
for (const edit of [...edits].sort((a, b) => b.start - a.start)) {
|
|
2277
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
2278
|
+
}
|
|
2279
|
+
return next;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/term/core/readers/xml.ts
|
|
2283
|
+
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
2284
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
2285
|
+
var LINE_BREAK = /\r\n|\r|\n|\u0085|\u2028|\u2029/g;
|
|
2286
|
+
function lineStarts(source) {
|
|
2287
|
+
const starts = [0];
|
|
2288
|
+
for (const match of source.matchAll(LINE_BREAK)) starts.push(match.index + match[0].length);
|
|
2289
|
+
return starts;
|
|
2290
|
+
}
|
|
2291
|
+
function parseXml(input) {
|
|
2292
|
+
const cached = cache.get(input);
|
|
2293
|
+
if (cached !== void 0) return cached;
|
|
2294
|
+
const shift = input.content.charCodeAt(0) === 65279 ? 1 : 0;
|
|
2295
|
+
const body = input.content.slice(shift);
|
|
2296
|
+
const errors = [];
|
|
2297
|
+
let parsed = null;
|
|
2298
|
+
try {
|
|
2299
|
+
const doc = new DOMParser2({
|
|
2300
|
+
onError: (level, message) => {
|
|
2301
|
+
if (level !== "error" && level !== "fatalError") return;
|
|
2302
|
+
if (/entity not found/i.test(message)) return;
|
|
2303
|
+
errors.push(message);
|
|
2304
|
+
}
|
|
2305
|
+
}).parseFromString(body, "text/xml");
|
|
2306
|
+
const root = doc.documentElement;
|
|
2307
|
+
if (errors.length === 0 && root !== null) parsed = { root, body, shift, starts: lineStarts(body) };
|
|
2308
|
+
} catch {
|
|
2309
|
+
parsed = null;
|
|
2310
|
+
}
|
|
2311
|
+
cache.set(input, parsed);
|
|
2312
|
+
return parsed;
|
|
2313
|
+
}
|
|
2314
|
+
function nameOf2(el) {
|
|
2315
|
+
return (el.localName ?? el.nodeName).toLowerCase();
|
|
2316
|
+
}
|
|
2317
|
+
function childrenOf(el, name) {
|
|
2318
|
+
const out = [];
|
|
2319
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
2320
|
+
const child = el.children.item(i);
|
|
2321
|
+
if (child !== null && (name === void 0 || nameOf2(child) === name)) out.push(child);
|
|
2322
|
+
}
|
|
2323
|
+
return out;
|
|
2324
|
+
}
|
|
2325
|
+
function descendantsOf(el, name) {
|
|
2326
|
+
const out = [];
|
|
2327
|
+
const walk = (parent) => {
|
|
2328
|
+
for (const child of childrenOf(parent)) {
|
|
2329
|
+
if (nameOf2(child) === name) out.push(child);
|
|
2330
|
+
walk(child);
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
walk(el);
|
|
2334
|
+
return out;
|
|
2335
|
+
}
|
|
2336
|
+
function textOfElement(el) {
|
|
2337
|
+
return collapse(el.textContent ?? "");
|
|
2338
|
+
}
|
|
2339
|
+
function lineOfElement(el) {
|
|
2340
|
+
return el.lineNumber ?? 1;
|
|
2341
|
+
}
|
|
2342
|
+
function skipOpaque(body, from) {
|
|
2343
|
+
const opaque = [
|
|
2344
|
+
["<!--", "-->"],
|
|
2345
|
+
["<![CDATA[", "]]>"],
|
|
2346
|
+
["<?", "?>"],
|
|
2347
|
+
["<!", ">"]
|
|
2348
|
+
];
|
|
2349
|
+
for (const [open, close] of opaque) {
|
|
2350
|
+
if (body.startsWith(open, from)) {
|
|
2351
|
+
const end = body.indexOf(close, from + open.length);
|
|
2352
|
+
return end === -1 ? body.length : end + close.length;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
return void 0;
|
|
2356
|
+
}
|
|
2357
|
+
function tagEnd(body, from) {
|
|
2358
|
+
let quote;
|
|
2359
|
+
for (let i = from + 1; i < body.length; i++) {
|
|
2360
|
+
const ch = body[i];
|
|
2361
|
+
if (quote !== void 0) {
|
|
2362
|
+
if (ch === quote) quote = void 0;
|
|
2363
|
+
} else if (ch === '"' || ch === "'") {
|
|
2364
|
+
quote = ch;
|
|
2365
|
+
} else if (ch === ">") {
|
|
2366
|
+
return { end: i + 1, selfClosing: body[i - 1] === "/" };
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return void 0;
|
|
2370
|
+
}
|
|
2371
|
+
function namedAt(body, at, name) {
|
|
2372
|
+
if (!body.startsWith(name, at)) return false;
|
|
2373
|
+
const after = body[at + name.length];
|
|
2374
|
+
return after === void 0 || /[\s/>]/.test(after);
|
|
2375
|
+
}
|
|
2376
|
+
function spanOfElement(xml, el) {
|
|
2377
|
+
const lineStart = xml.starts[(el.lineNumber ?? 1) - 1];
|
|
2378
|
+
if (lineStart === void 0) return void 0;
|
|
2379
|
+
const start2 = lineStart + (el.columnNumber ?? 1) - 1;
|
|
2380
|
+
const { body } = xml;
|
|
2381
|
+
if (body[start2] !== "<") return void 0;
|
|
2382
|
+
const name = el.nodeName;
|
|
2383
|
+
const open = tagEnd(body, start2);
|
|
2384
|
+
if (open === void 0) return void 0;
|
|
2385
|
+
if (open.selfClosing) return { start: start2 + xml.shift, end: open.end + xml.shift };
|
|
2386
|
+
let depth = 1;
|
|
2387
|
+
let i = open.end;
|
|
2388
|
+
while (i < body.length) {
|
|
2389
|
+
const lt = body.indexOf("<", i);
|
|
2390
|
+
if (lt === -1) return void 0;
|
|
2391
|
+
const skipTo = skipOpaque(body, lt);
|
|
2392
|
+
if (skipTo !== void 0) {
|
|
2393
|
+
i = skipTo;
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
const tag = tagEnd(body, lt);
|
|
2397
|
+
if (tag === void 0) return void 0;
|
|
2398
|
+
if (body[lt + 1] === "/" && namedAt(body, lt + 2, name)) {
|
|
2399
|
+
depth -= 1;
|
|
2400
|
+
if (depth === 0) return { start: start2 + xml.shift, end: tag.end + xml.shift };
|
|
2401
|
+
} else if (namedAt(body, lt + 1, name) && !tag.selfClosing) {
|
|
2402
|
+
depth += 1;
|
|
2403
|
+
}
|
|
2404
|
+
i = tag.end;
|
|
2405
|
+
}
|
|
2406
|
+
return void 0;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/term/core/readers/dita.ts
|
|
2410
|
+
function definitionOf(glossdef) {
|
|
2411
|
+
const paragraphs = childrenOf(glossdef, "p");
|
|
2412
|
+
if (paragraphs.length === 0) return textOfElement(glossdef);
|
|
2413
|
+
return paragraphs.map(textOfElement).filter((text2) => text2 !== "").join("\n\n");
|
|
2414
|
+
}
|
|
2415
|
+
function readEntry(input, xml, entry, construct, label) {
|
|
2416
|
+
const raw = {};
|
|
2417
|
+
const lines = {};
|
|
2418
|
+
const [glossterm] = childrenOf(entry, "glossterm");
|
|
2419
|
+
if (glossterm !== void 0) {
|
|
2420
|
+
raw.label = textOfElement(glossterm);
|
|
2421
|
+
lines.label = lineOfElement(glossterm);
|
|
2422
|
+
}
|
|
2423
|
+
const [glossdef] = childrenOf(entry, "glossdef");
|
|
2424
|
+
if (glossdef !== void 0) {
|
|
2425
|
+
raw.definition = definitionOf(glossdef);
|
|
2426
|
+
lines.definition = lineOfElement(glossdef);
|
|
2427
|
+
}
|
|
2428
|
+
const alts = [];
|
|
2429
|
+
for (const body of childrenOf(entry, "glossbody")) {
|
|
2430
|
+
const [usage] = childrenOf(body, "glossusage");
|
|
2431
|
+
if (usage !== void 0 && raw["scope-note"] === void 0) {
|
|
2432
|
+
raw["scope-note"] = textOfElement(usage);
|
|
2433
|
+
lines["scope-note"] = lineOfElement(usage);
|
|
2434
|
+
}
|
|
2435
|
+
for (const alt of childrenOf(body, "glossalt")) {
|
|
2436
|
+
for (const form of childrenOf(alt)) {
|
|
2437
|
+
const name = nameOf2(form);
|
|
2438
|
+
if (name !== "glosssynonym" && name !== "glossacronym") continue;
|
|
2439
|
+
alts.push(textOfElement(form));
|
|
2440
|
+
lines["alt-labels"] ??= lineOfElement(form);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
raw["alt-labels"] = alts;
|
|
2445
|
+
const line = lineOfElement(entry);
|
|
2446
|
+
const record = recordOf(raw);
|
|
2447
|
+
if (record === void 0) return skipped(input, line, label);
|
|
2448
|
+
const span = spanOfElement(xml, entry);
|
|
2449
|
+
return termOf(input, {
|
|
2450
|
+
record,
|
|
2451
|
+
constructId: entry.getAttribute("id"),
|
|
2452
|
+
construct,
|
|
2453
|
+
line,
|
|
2454
|
+
lines,
|
|
2455
|
+
...span === void 0 ? {} : { span }
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
function collect3(input, xml, entries, construct, label) {
|
|
2459
|
+
const result = nothing();
|
|
2460
|
+
for (const entry of entries) {
|
|
2461
|
+
const read8 = readEntry(input, xml, entry, construct, label);
|
|
2462
|
+
if (typeof read8 === "string") result.notices.push(read8);
|
|
2463
|
+
else result.terms.push(read8);
|
|
2464
|
+
}
|
|
2465
|
+
return result;
|
|
2466
|
+
}
|
|
2467
|
+
var GLOSSENTRY_LABEL = "DITA glossentry";
|
|
2468
|
+
var GLOSSGROUP_LABEL = "DITA glossgroup";
|
|
2469
|
+
function readGlossentry(input) {
|
|
2470
|
+
const xml = parseXml(input);
|
|
2471
|
+
if (xml === null || nameOf2(xml.root) !== "glossentry") return nothing();
|
|
2472
|
+
return collect3(input, xml, [xml.root], "dita-glossentry", GLOSSENTRY_LABEL);
|
|
2473
|
+
}
|
|
2474
|
+
function readGlossgroup(input) {
|
|
2475
|
+
const xml = parseXml(input);
|
|
2476
|
+
if (xml === null || nameOf2(xml.root) !== "glossgroup") return nothing();
|
|
2477
|
+
return collect3(input, xml, descendantsOf(xml.root, "glossentry"), "dita-glossgroup", GLOSSGROUP_LABEL);
|
|
2478
|
+
}
|
|
2479
|
+
var ditaGlossentryReader = {
|
|
2480
|
+
construct: "dita-glossentry",
|
|
2481
|
+
label: GLOSSENTRY_LABEL,
|
|
2482
|
+
formats: ["xml"],
|
|
2483
|
+
read: readGlossentry,
|
|
2484
|
+
// The glossentry is the file's root, so the file's `xml:lang` sits on it and
|
|
2485
|
+
// is written again when the entry is.
|
|
2486
|
+
apply: (input, terms) => applyEntries(input, readGlossentry, terms, {
|
|
2487
|
+
holds: DITA_HOLDS,
|
|
2488
|
+
id: true,
|
|
2489
|
+
serialize: (term, file) => ditaEntry(term, textOf(file.metadata["xml:lang"]) === void 0 ? void 0 : term.language)
|
|
2490
|
+
})
|
|
2491
|
+
};
|
|
2492
|
+
var ditaGlossgroupReader = {
|
|
2493
|
+
construct: "dita-glossgroup",
|
|
2494
|
+
label: GLOSSGROUP_LABEL,
|
|
2495
|
+
formats: ["xml"],
|
|
2496
|
+
read: readGlossgroup,
|
|
2497
|
+
apply: (input, terms) => applyEntries(input, readGlossgroup, terms, { holds: DITA_HOLDS, id: true, serialize: (term) => ditaEntry(term) })
|
|
2498
|
+
};
|
|
2499
|
+
|
|
2500
|
+
// src/term/core/readers/docbook.ts
|
|
2501
|
+
var LABEL = "DocBook glossary";
|
|
2502
|
+
function referenceOf(el) {
|
|
2503
|
+
return textOf(textOfElement(el)) ?? textOf(el.getAttribute("otherterm"));
|
|
2504
|
+
}
|
|
2505
|
+
function definitionOf2(glossdef) {
|
|
2506
|
+
const children = childrenOf(glossdef);
|
|
2507
|
+
const blocks = children.filter((child) => nameOf2(child) !== "glossseealso");
|
|
2508
|
+
if (blocks.length === 0) return children.length === 0 ? textOfElement(glossdef) : "";
|
|
2509
|
+
return blocks.map(textOfElement).filter((text2) => text2 !== "").join("\n\n");
|
|
2510
|
+
}
|
|
2511
|
+
function read(input) {
|
|
2512
|
+
const xml = parseXml(input);
|
|
2513
|
+
if (xml === null) return nothing();
|
|
2514
|
+
const glossaries = nameOf2(xml.root) === "glossary" ? [xml.root] : descendantsOf(xml.root, "glossary");
|
|
2515
|
+
if (glossaries.length === 0) return nothing();
|
|
2516
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2517
|
+
const result = nothing();
|
|
2518
|
+
for (const glossary of glossaries) {
|
|
2519
|
+
for (const entry of descendantsOf(glossary, "glossentry")) {
|
|
2520
|
+
if (seen.has(entry)) continue;
|
|
2521
|
+
seen.add(entry);
|
|
2522
|
+
const raw = {};
|
|
2523
|
+
const lines = {};
|
|
2524
|
+
const alts = [];
|
|
2525
|
+
const definitions = [];
|
|
2526
|
+
const related = [];
|
|
2527
|
+
for (const child of childrenOf(entry)) {
|
|
2528
|
+
const name = nameOf2(child);
|
|
2529
|
+
if (name === "glossterm" && raw.label === void 0) {
|
|
2530
|
+
raw.label = textOfElement(child);
|
|
2531
|
+
lines.label = lineOfElement(child);
|
|
2532
|
+
} else if (name === "glossterm" || name === "acronym") {
|
|
2533
|
+
alts.push(textOfElement(child));
|
|
2534
|
+
lines["alt-labels"] ??= lineOfElement(child);
|
|
2535
|
+
} else if (name === "glossdef") {
|
|
2536
|
+
definitions.push(definitionOf2(child));
|
|
2537
|
+
lines.definition ??= lineOfElement(child);
|
|
2538
|
+
for (const seeAlso of childrenOf(child, "glossseealso")) {
|
|
2539
|
+
const target = referenceOf(seeAlso);
|
|
2540
|
+
if (target !== void 0) related.push(target);
|
|
2541
|
+
lines["related-terms"] ??= lineOfElement(seeAlso);
|
|
2542
|
+
}
|
|
2543
|
+
} else if (name === "glosssee" && raw.see === void 0) {
|
|
2544
|
+
raw.see = referenceOf(child);
|
|
2545
|
+
lines.see = lineOfElement(child);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
raw["alt-labels"] = alts;
|
|
2549
|
+
raw.definition = definitions.filter((text2) => text2 !== "").join("\n\n");
|
|
2550
|
+
raw["related-terms"] = related;
|
|
2551
|
+
const line = lineOfElement(entry);
|
|
2552
|
+
const record = recordOf(raw);
|
|
2553
|
+
if (record === void 0) {
|
|
2554
|
+
result.notices.push(skipped(input, line, LABEL));
|
|
2555
|
+
continue;
|
|
2556
|
+
}
|
|
2557
|
+
const span = spanOfElement(xml, entry);
|
|
2558
|
+
result.terms.push(
|
|
2559
|
+
termOf(input, {
|
|
2560
|
+
record,
|
|
2561
|
+
constructId: entry.getAttribute("xml:id"),
|
|
2562
|
+
construct: "docbook-glossary",
|
|
2563
|
+
line,
|
|
2564
|
+
lines,
|
|
2565
|
+
...span === void 0 ? {} : { span }
|
|
2566
|
+
})
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
return result;
|
|
2571
|
+
}
|
|
2572
|
+
var docbookGlossaryReader = {
|
|
2573
|
+
construct: "docbook-glossary",
|
|
2574
|
+
label: LABEL,
|
|
2575
|
+
formats: ["xml"],
|
|
2576
|
+
read,
|
|
2577
|
+
apply: (input, terms) => applyEntries(input, read, terms, { holds: DOCBOOK_HOLDS, id: true, serialize: docbookEntry })
|
|
2578
|
+
};
|
|
2579
|
+
|
|
2580
|
+
// src/term/core/readers/html.ts
|
|
2581
|
+
import { parse as parse2 } from "parse5";
|
|
2582
|
+
var cache2 = /* @__PURE__ */ new WeakMap();
|
|
2583
|
+
function parseHtml(input) {
|
|
2584
|
+
const cached = cache2.get(input);
|
|
2585
|
+
if (cached !== void 0) return cached;
|
|
2586
|
+
const shift = input.content.charCodeAt(0) === 65279 ? 1 : 0;
|
|
2587
|
+
const parsed = { root: parse2(input.content.slice(shift), { sourceCodeLocationInfo: true }), shift };
|
|
2588
|
+
cache2.set(input, parsed);
|
|
2589
|
+
return parsed;
|
|
2590
|
+
}
|
|
2591
|
+
function isElement(node) {
|
|
2592
|
+
return "tagName" in node;
|
|
2593
|
+
}
|
|
2594
|
+
function childElements2(node) {
|
|
2595
|
+
return node.childNodes.filter(isElement);
|
|
2596
|
+
}
|
|
2597
|
+
function elementsNamed(node, name) {
|
|
2598
|
+
const out = [];
|
|
2599
|
+
const walk = (parent) => {
|
|
2600
|
+
for (const child of childElements2(parent)) {
|
|
2601
|
+
if (child.tagName === name) out.push(child);
|
|
2602
|
+
walk(child);
|
|
2603
|
+
}
|
|
2604
|
+
};
|
|
2605
|
+
walk(node);
|
|
2606
|
+
return out;
|
|
2607
|
+
}
|
|
2608
|
+
function rawText(node) {
|
|
2609
|
+
let text2 = "";
|
|
2610
|
+
for (const child of node.childNodes) {
|
|
2611
|
+
if (child.nodeName === "#text" && "value" in child) text2 += child.value;
|
|
2612
|
+
else if (isElement(child)) text2 += rawText(child);
|
|
2613
|
+
}
|
|
2614
|
+
return text2;
|
|
2615
|
+
}
|
|
2616
|
+
function textOfNode(node) {
|
|
2617
|
+
return collapse(rawText(node));
|
|
2618
|
+
}
|
|
2619
|
+
function attr(el, name) {
|
|
2620
|
+
return el.attrs.find((a) => a.name === name)?.value;
|
|
2621
|
+
}
|
|
2622
|
+
function lineOf(el) {
|
|
2623
|
+
return el.sourceCodeLocation?.startLine ?? 1;
|
|
2624
|
+
}
|
|
2625
|
+
function spanOf(parsed, first, last) {
|
|
2626
|
+
const start2 = first.sourceCodeLocation?.startOffset;
|
|
2627
|
+
const end = last.sourceCodeLocation?.endOffset;
|
|
2628
|
+
if (start2 === void 0 || end === void 0) return void 0;
|
|
2629
|
+
return { start: start2 + parsed.shift, end: end + parsed.shift };
|
|
2630
|
+
}
|
|
2631
|
+
function definitionOf3(dd) {
|
|
2632
|
+
const paragraphs = childElements2(dd).filter((child) => child.tagName === "p");
|
|
2633
|
+
if (paragraphs.length === 0) return textOfNode(dd);
|
|
2634
|
+
return paragraphs.map(textOfNode).filter((text2) => text2 !== "").join("\n\n");
|
|
2635
|
+
}
|
|
2636
|
+
function listItems(dl) {
|
|
2637
|
+
return childElements2(dl).flatMap((child) => child.tagName === "div" ? childElements2(child) : [child]);
|
|
2638
|
+
}
|
|
2639
|
+
var DL_LABEL = "dl";
|
|
2640
|
+
function readDl(input) {
|
|
2641
|
+
if (!declares(input, "term-set")) return nothing();
|
|
2642
|
+
const parsed = parseHtml(input);
|
|
2643
|
+
const result = nothing();
|
|
2644
|
+
for (const dl of elementsNamed(parsed.root, "dl")) {
|
|
2645
|
+
const items = listItems(dl).filter((item2) => item2.tagName === "dt" || item2.tagName === "dd");
|
|
2646
|
+
let i = 0;
|
|
2647
|
+
while (i < items.length) {
|
|
2648
|
+
const dts = [];
|
|
2649
|
+
const dds = [];
|
|
2650
|
+
for (let item2 = items[i]; item2?.tagName === "dt"; item2 = items[++i]) dts.push(item2);
|
|
2651
|
+
for (let item2 = items[i]; item2?.tagName === "dd"; item2 = items[++i]) dds.push(item2);
|
|
2652
|
+
const first = dts[0] ?? dds[0];
|
|
2653
|
+
const last = dds[dds.length - 1] ?? dts[dts.length - 1];
|
|
2654
|
+
if (first === void 0 || last === void 0) break;
|
|
2655
|
+
const named = dts.filter((dt) => textOfNode(dt) !== "");
|
|
2656
|
+
const [labelDt, ...altDts] = named;
|
|
2657
|
+
const [dd] = dds;
|
|
2658
|
+
const raw = {
|
|
2659
|
+
label: labelDt === void 0 ? void 0 : textOfNode(labelDt),
|
|
2660
|
+
"alt-labels": altDts.map(textOfNode),
|
|
2661
|
+
definition: dd === void 0 ? void 0 : definitionOf3(dd)
|
|
2662
|
+
};
|
|
2663
|
+
const lines = {
|
|
2664
|
+
label: labelDt === void 0 ? void 0 : lineOf(labelDt),
|
|
2665
|
+
"alt-labels": altDts[0] === void 0 ? void 0 : lineOf(altDts[0]),
|
|
2666
|
+
definition: dd === void 0 ? void 0 : lineOf(dd)
|
|
2667
|
+
};
|
|
2668
|
+
const line = lineOf(first);
|
|
2669
|
+
const record = recordOf(raw);
|
|
2670
|
+
if (record === void 0) {
|
|
2671
|
+
result.notices.push(skipped(input, line, DL_LABEL));
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
const span = spanOf(parsed, first, last);
|
|
2675
|
+
const constructId = dts.map((dt) => textOf(attr(dt, "id"))).find((id) => id !== void 0);
|
|
2676
|
+
result.terms.push(
|
|
2677
|
+
termOf(input, {
|
|
2678
|
+
record,
|
|
2679
|
+
constructId,
|
|
2680
|
+
construct: "html-dl",
|
|
2681
|
+
line,
|
|
2682
|
+
lines,
|
|
2683
|
+
...span === void 0 ? {} : { span }
|
|
2684
|
+
})
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
return result;
|
|
2689
|
+
}
|
|
2690
|
+
var DFN_LABEL = "dfn";
|
|
2691
|
+
function insideDt(el) {
|
|
2692
|
+
let parent = el.parentNode;
|
|
2693
|
+
while (parent !== null && "tagName" in parent) {
|
|
2694
|
+
if (parent.tagName === "dt") return true;
|
|
2695
|
+
parent = parent.parentNode;
|
|
2696
|
+
}
|
|
2697
|
+
return false;
|
|
2698
|
+
}
|
|
2699
|
+
function dfnFields(dfn) {
|
|
2700
|
+
const title = textOf(attr(dfn, "title"));
|
|
2701
|
+
if (title !== void 0) return { label: title };
|
|
2702
|
+
const content = dfn.childNodes.filter(
|
|
2703
|
+
(child) => child.nodeName === "#text" && "value" in child ? child.value.trim() !== "" : isElement(child)
|
|
2704
|
+
);
|
|
2705
|
+
const [only] = content;
|
|
2706
|
+
if (content.length === 1 && only !== void 0 && isElement(only) && only.tagName === "abbr") {
|
|
2707
|
+
const abbrTitle = textOf(attr(only, "title"));
|
|
2708
|
+
if (abbrTitle !== void 0) return { label: abbrTitle, alt: textOfNode(only) };
|
|
2709
|
+
}
|
|
2710
|
+
return { label: textOfNode(dfn) };
|
|
2711
|
+
}
|
|
2712
|
+
function readDfn(input) {
|
|
2713
|
+
if (!declares(input, "term-set")) return nothing();
|
|
2714
|
+
const parsed = parseHtml(input);
|
|
2715
|
+
const result = nothing();
|
|
2716
|
+
for (const dfn of elementsNamed(parsed.root, "dfn")) {
|
|
2717
|
+
if (insideDt(dfn)) continue;
|
|
2718
|
+
const { label, alt } = dfnFields(dfn);
|
|
2719
|
+
const line = lineOf(dfn);
|
|
2720
|
+
const record = recordOf({ label, "alt-labels": alt });
|
|
2721
|
+
if (record === void 0) {
|
|
2722
|
+
result.notices.push(skipped(input, line, DFN_LABEL));
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
const span = spanOf(parsed, dfn, dfn);
|
|
2726
|
+
const term = termOf(input, {
|
|
2727
|
+
record,
|
|
2728
|
+
constructId: attr(dfn, "id"),
|
|
2729
|
+
construct: "html-dfn",
|
|
2730
|
+
line,
|
|
2731
|
+
lines: { label: line, "alt-labels": line },
|
|
2732
|
+
...span === void 0 ? {} : { span }
|
|
2733
|
+
});
|
|
2734
|
+
result.terms.push(term);
|
|
2735
|
+
}
|
|
2736
|
+
return result;
|
|
2737
|
+
}
|
|
2738
|
+
var htmlDlReader = {
|
|
2739
|
+
construct: "html-dl",
|
|
2740
|
+
label: DL_LABEL,
|
|
2741
|
+
formats: ["html"],
|
|
2742
|
+
read: readDl,
|
|
2743
|
+
apply: (input, terms) => applyEntries(input, readDl, terms, { holds: LIST_HOLDS, id: true, serialize: htmlDlEntry })
|
|
2744
|
+
};
|
|
2745
|
+
var htmlDfnReader = {
|
|
2746
|
+
construct: "html-dfn",
|
|
2747
|
+
label: DFN_LABEL,
|
|
2748
|
+
formats: ["html"],
|
|
2749
|
+
read: readDfn
|
|
2750
|
+
};
|
|
2751
|
+
|
|
2752
|
+
// src/term/core/readers/manifest.ts
|
|
2753
|
+
import { isMap, isNode, isScalar, isSeq, LineCounter, parseDocument, stringify as stringifyYaml } from "yaml";
|
|
2754
|
+
var LABEL2 = "manifest";
|
|
2755
|
+
var FIELDS = new Set(TERM_FIELDS);
|
|
2756
|
+
function isField(key) {
|
|
2757
|
+
return FIELDS.has(key);
|
|
2758
|
+
}
|
|
2759
|
+
function read2(input) {
|
|
2760
|
+
if ((input.path ?? input.file).toLowerCase().endsWith(".json")) {
|
|
2761
|
+
try {
|
|
2762
|
+
JSON.parse(input.content);
|
|
2763
|
+
} catch (error) {
|
|
2764
|
+
throw new TermError(`${input.file}: could not be parsed as JSON: ${errorMessage(error)}`);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const lineCounter = new LineCounter();
|
|
2768
|
+
const doc = parseDocument(input.content, { lineCounter, prettyErrors: false });
|
|
2769
|
+
const [firstError] = doc.errors;
|
|
2770
|
+
if (firstError !== void 0) {
|
|
2771
|
+
throw new TermError(`${input.file}: could not be parsed as YAML: ${firstError.message}`);
|
|
2772
|
+
}
|
|
2773
|
+
const lineOf2 = (node) => {
|
|
2774
|
+
const range = node?.range;
|
|
2775
|
+
return range === void 0 || range === null ? void 0 : lineCounter.linePos(range[0]).line;
|
|
2776
|
+
};
|
|
2777
|
+
const root = doc.contents;
|
|
2778
|
+
if (root === null) return { terms: [], notices: [] };
|
|
2779
|
+
if (!isMap(root)) {
|
|
2780
|
+
throw new TermError(`${input.file}: a term manifest is a mapping of id to entry.`);
|
|
2781
|
+
}
|
|
2782
|
+
const terms = [];
|
|
2783
|
+
const notices = [];
|
|
2784
|
+
for (const pair of root.items) {
|
|
2785
|
+
const keyNode = isScalar(pair.key) ? pair.key : void 0;
|
|
2786
|
+
const id = textOf(keyNode?.value);
|
|
2787
|
+
const line = lineOf2(keyNode) ?? 1;
|
|
2788
|
+
if (id === void 0) {
|
|
2789
|
+
throw new TermError(`${input.file}:${line}: an entry's key is its id, and must be a non-empty string.`);
|
|
2790
|
+
}
|
|
2791
|
+
const value = pair.value;
|
|
2792
|
+
if (!isMap(value)) {
|
|
2793
|
+
throw new TermError(`${input.file}:${line}: entry "${id}" is not a mapping of fields.`);
|
|
2794
|
+
}
|
|
2795
|
+
const raw = {};
|
|
2796
|
+
const lines = {};
|
|
2797
|
+
for (const field of value.items) {
|
|
2798
|
+
const fieldKey = isScalar(field.key) ? field.key : void 0;
|
|
2799
|
+
const name = fieldKey?.value;
|
|
2800
|
+
if (typeof name !== "string" || !isField(name)) continue;
|
|
2801
|
+
const fieldValue = field.value;
|
|
2802
|
+
raw[name] = isNode(fieldValue) ? fieldValue.toJSON() : fieldValue;
|
|
2803
|
+
lines[name] = lineOf2(fieldKey);
|
|
2804
|
+
}
|
|
2805
|
+
const record = recordOf(raw);
|
|
2806
|
+
if (record === void 0) {
|
|
2807
|
+
notices.push(skipped(input, line, LABEL2));
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
for (const ignored of ignoredFields(raw)) {
|
|
2811
|
+
notices.push(ignoredNotice(input, lines[ignored.field] ?? line, record.label, ignored));
|
|
2812
|
+
}
|
|
2813
|
+
terms.push(termOf(input, { record, recordId: id, construct: "manifest", line, lines }));
|
|
2814
|
+
}
|
|
2815
|
+
return { terms, notices };
|
|
2816
|
+
}
|
|
2817
|
+
function entryOf(record) {
|
|
2818
|
+
const entry = {};
|
|
2819
|
+
for (const field of TERM_FIELDS) {
|
|
2820
|
+
const value = record[field];
|
|
2821
|
+
if (value !== void 0) entry[field] = value;
|
|
2822
|
+
}
|
|
2823
|
+
return entry;
|
|
2824
|
+
}
|
|
2825
|
+
function currentRecord(value) {
|
|
2826
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
2827
|
+
const raw = {};
|
|
2828
|
+
for (const field of TERM_FIELDS) raw[field] = value[field];
|
|
2829
|
+
return recordOf(raw) ?? {};
|
|
2830
|
+
}
|
|
2831
|
+
function changedFields(current, record) {
|
|
2832
|
+
return TERM_FIELDS.filter((field) => JSON.stringify(current[field]) !== JSON.stringify(record[field]));
|
|
2833
|
+
}
|
|
2834
|
+
function applyJson(input, terms) {
|
|
2835
|
+
let data;
|
|
2836
|
+
try {
|
|
2837
|
+
data = JSON.parse(input.content);
|
|
2838
|
+
} catch (error) {
|
|
2839
|
+
throw new TermError(`${input.file}: could not be parsed as JSON: ${errorMessage(error)}`);
|
|
2840
|
+
}
|
|
2841
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
2842
|
+
throw new TermError(`${input.file}: a term manifest is a mapping of id to entry.`);
|
|
2843
|
+
}
|
|
2844
|
+
const root = data;
|
|
2845
|
+
let changed = false;
|
|
2846
|
+
for (const term of terms) {
|
|
2847
|
+
const existing = root[term.id];
|
|
2848
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
2849
|
+
if (existing !== void 0) {
|
|
2850
|
+
throw new TermError(`${input.file}: entry "${term.id}" is not a mapping of fields.`);
|
|
2851
|
+
}
|
|
2852
|
+
root[term.id] = entryOf(term.record);
|
|
2853
|
+
changed = true;
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
const entry = existing;
|
|
2857
|
+
const fields = changedFields(currentRecord(entry), term.record);
|
|
2858
|
+
if (fields.length === 0) continue;
|
|
2859
|
+
const next = {};
|
|
2860
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
2861
|
+
if (!isField(key) || !fields.includes(key)) next[key] = value;
|
|
2862
|
+
else if (term.record[key] !== void 0) next[key] = term.record[key];
|
|
2863
|
+
}
|
|
2864
|
+
for (const field of fields) {
|
|
2865
|
+
if (term.record[field] !== void 0 && !(field in next)) next[field] = term.record[field];
|
|
2866
|
+
}
|
|
2867
|
+
root[term.id] = next;
|
|
2868
|
+
changed = true;
|
|
2869
|
+
}
|
|
2870
|
+
if (!changed) return input.content;
|
|
2871
|
+
const text2 = `${JSON.stringify(root, null, 2)}
|
|
2872
|
+
`;
|
|
2873
|
+
return input.content.includes("\r\n") ? text2.replaceAll("\n", "\r\n") : text2;
|
|
2874
|
+
}
|
|
2875
|
+
function entryRanges(root) {
|
|
2876
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
2877
|
+
for (const pair of root.items) {
|
|
2878
|
+
if (!isScalar(pair.key)) continue;
|
|
2879
|
+
const id = textOf(pair.key.value);
|
|
2880
|
+
const start2 = pair.key.range?.[0];
|
|
2881
|
+
const end = isNode(pair.value) ? pair.value.range?.[1] : pair.key.range?.[1];
|
|
2882
|
+
if (id !== void 0 && start2 !== void 0 && end !== void 0) ranges.set(id, { start: start2, end });
|
|
2883
|
+
}
|
|
2884
|
+
return ranges;
|
|
2885
|
+
}
|
|
2886
|
+
function editEntry(doc, entry, record) {
|
|
2887
|
+
const fields = changedFields(currentRecord(entry.toJSON()), record);
|
|
2888
|
+
for (const field of fields) {
|
|
2889
|
+
const value = record[field];
|
|
2890
|
+
if (value === void 0) {
|
|
2891
|
+
entry.delete(field);
|
|
2892
|
+
} else if (Array.isArray(value)) {
|
|
2893
|
+
const previous = entry.get(field, true);
|
|
2894
|
+
const node = doc.createNode(value);
|
|
2895
|
+
node.flow = isSeq(previous) && previous.flow === true;
|
|
2896
|
+
entry.set(field, node);
|
|
2897
|
+
} else {
|
|
2898
|
+
entry.set(field, value);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
return fields.length > 0;
|
|
2902
|
+
}
|
|
2903
|
+
function applyYaml(input, terms) {
|
|
2904
|
+
const { content } = input;
|
|
2905
|
+
const doc = parseDocument(content, { prettyErrors: false });
|
|
2906
|
+
const [firstError] = doc.errors;
|
|
2907
|
+
if (firstError !== void 0) {
|
|
2908
|
+
throw new TermError(`${input.file}: could not be parsed as YAML: ${firstError.message}`);
|
|
2909
|
+
}
|
|
2910
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
2911
|
+
const root = doc.contents;
|
|
2912
|
+
if (root === null) {
|
|
2913
|
+
if (terms.length === 0) return content;
|
|
2914
|
+
const entries = Object.fromEntries(terms.map((term) => [term.id, entryOf(term.record)]));
|
|
2915
|
+
return stringifyYaml(entries, { lineWidth: 0 }).replaceAll("\n", eol);
|
|
2916
|
+
}
|
|
2917
|
+
if (!isMap(root)) {
|
|
2918
|
+
throw new TermError(`${input.file}: a term manifest is a mapping of id to entry.`);
|
|
2919
|
+
}
|
|
2920
|
+
const wholeFile = root.flow === true;
|
|
2921
|
+
const before = entryRanges(root);
|
|
2922
|
+
const changed = [];
|
|
2923
|
+
const added = [];
|
|
2924
|
+
for (const term of terms) {
|
|
2925
|
+
const pair = root.items.find((item2) => isScalar(item2.key) && textOf(item2.key.value) === term.id);
|
|
2926
|
+
if (pair === void 0) {
|
|
2927
|
+
root.add(doc.createPair(term.id, entryOf(term.record)));
|
|
2928
|
+
added.push(term.id);
|
|
2929
|
+
continue;
|
|
2930
|
+
}
|
|
2931
|
+
if (!isMap(pair.value)) {
|
|
2932
|
+
throw new TermError(`${input.file}: entry "${term.id}" is not a mapping of fields.`);
|
|
2933
|
+
}
|
|
2934
|
+
if (editEntry(doc, pair.value, term.record)) changed.push(term.id);
|
|
2935
|
+
}
|
|
2936
|
+
if (changed.length === 0 && added.length === 0) return content;
|
|
2937
|
+
const text2 = doc.toString({ lineWidth: 0 });
|
|
2938
|
+
if (wholeFile) return text2.replaceAll("\n", eol);
|
|
2939
|
+
const written = parseDocument(text2).contents;
|
|
2940
|
+
const after = isMap(written) ? entryRanges(written) : /* @__PURE__ */ new Map();
|
|
2941
|
+
const entryText = (id) => {
|
|
2942
|
+
const range = after.get(id);
|
|
2943
|
+
if (range === void 0) throw new TermError(`${input.file}: could not write entry "${id}".`);
|
|
2944
|
+
return text2.slice(range.start, range.end).replaceAll("\n", eol);
|
|
2945
|
+
};
|
|
2946
|
+
let next = content;
|
|
2947
|
+
const edits = changed.map((id) => ({ id, range: before.get(id) })).sort((a, b) => (b.range?.start ?? 0) - (a.range?.start ?? 0));
|
|
2948
|
+
for (const { id, range } of edits) {
|
|
2949
|
+
if (range === void 0) throw new TermError(`${input.file}: could not write entry "${id}".`);
|
|
2950
|
+
next = next.slice(0, range.start) + entryText(id) + next.slice(range.end);
|
|
2951
|
+
}
|
|
2952
|
+
if (added.length > 0) {
|
|
2953
|
+
if (next !== "" && !next.endsWith("\n")) next += eol;
|
|
2954
|
+
next += added.map((id) => `${entryText(id)}${eol}`).join("");
|
|
2955
|
+
}
|
|
2956
|
+
return next;
|
|
2957
|
+
}
|
|
2958
|
+
function isJson(input) {
|
|
2959
|
+
return (input.path ?? input.file).toLowerCase().endsWith(".json");
|
|
2960
|
+
}
|
|
2961
|
+
var manifestReader = {
|
|
2962
|
+
construct: "manifest",
|
|
2963
|
+
label: LABEL2,
|
|
2964
|
+
formats: ["manifest"],
|
|
2965
|
+
read: read2,
|
|
2966
|
+
apply: (input, terms) => isJson(input) ? applyJson(input, terms) : applyYaml(input, terms)
|
|
2967
|
+
};
|
|
2968
|
+
|
|
2969
|
+
// src/term/core/readers/page.ts
|
|
2970
|
+
var LABEL3 = "page";
|
|
2971
|
+
function read3(input) {
|
|
2972
|
+
if (!declares(input, "term")) return nothing();
|
|
2973
|
+
const raw = {};
|
|
2974
|
+
const lines = {};
|
|
2975
|
+
for (const field of TERM_FIELDS) {
|
|
2976
|
+
raw[field] = input.metadata[field];
|
|
2977
|
+
lines[field] = input.lineFor(`/${field}`);
|
|
2978
|
+
}
|
|
2979
|
+
const record = recordOf(raw);
|
|
2980
|
+
if (record === void 0) return { terms: [], notices: [skipped(input, 1, LABEL3)] };
|
|
2981
|
+
return {
|
|
2982
|
+
terms: [termOf(input, { record, recordId: input.metadata["id"], construct: "page", line: 1, lines })],
|
|
2983
|
+
notices: ignoredFields(raw).map(
|
|
2984
|
+
(ignored) => ignoredNotice(input, lines[ignored.field] ?? 1, record.label, ignored)
|
|
2985
|
+
)
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function apply(input, terms) {
|
|
2989
|
+
const [term] = terms;
|
|
2990
|
+
if (term === void 0) return input.content;
|
|
2991
|
+
const [current] = read3(input).terms;
|
|
2992
|
+
if (current === void 0) {
|
|
2993
|
+
throw new TermError(`${input.file}: is not a term page any more. Read the file again before writing it.`);
|
|
2994
|
+
}
|
|
2995
|
+
const patch = {};
|
|
2996
|
+
const deletions = [];
|
|
2997
|
+
for (const field of TERM_FIELDS) {
|
|
2998
|
+
if (sameFields(current.record, term.record, [field])) continue;
|
|
2999
|
+
const value = term.record[field];
|
|
3000
|
+
if (value === void 0) deletions.push(field);
|
|
3001
|
+
else patch[field] = value;
|
|
3002
|
+
}
|
|
3003
|
+
if (Object.keys(patch).length === 0 && deletions.length === 0) return input.content;
|
|
3004
|
+
const extractor = extractorByName(input.format);
|
|
3005
|
+
if (extractor?.apply === void 0) {
|
|
3006
|
+
throw new TermError(`${input.file}: manni cannot write ${input.format} metadata.`);
|
|
3007
|
+
}
|
|
3008
|
+
const filePath = input.path ?? input.file;
|
|
3009
|
+
let next;
|
|
3010
|
+
try {
|
|
3011
|
+
next = extractor.apply(input.content, patch, { deletions, filePath });
|
|
3012
|
+
} catch (error) {
|
|
3013
|
+
throw new TermError(`${input.file}: ${errorMessage(error)}`);
|
|
3014
|
+
}
|
|
3015
|
+
const written = extractor.extract(next, filePath).data;
|
|
3016
|
+
for (const field of deletions) {
|
|
3017
|
+
if (written[field] !== void 0) {
|
|
3018
|
+
throw new TermError(`${input.file}: could not remove ${field} from its ${input.format} metadata. Remove it by hand.`);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return next;
|
|
3022
|
+
}
|
|
3023
|
+
var pageReader = {
|
|
3024
|
+
construct: "page",
|
|
3025
|
+
label: LABEL3,
|
|
3026
|
+
formats: ["markdown", "mdx", "asciidoc", "rst", "html", "xml"],
|
|
3027
|
+
read: read3,
|
|
3028
|
+
apply
|
|
3029
|
+
};
|
|
3030
|
+
|
|
3031
|
+
// src/term/core/readers/structured.ts
|
|
3032
|
+
var STRUCTURED_READERS = [
|
|
3033
|
+
pageReader,
|
|
3034
|
+
manifestReader,
|
|
3035
|
+
ditaGlossentryReader,
|
|
3036
|
+
ditaGlossgroupReader,
|
|
3037
|
+
docbookGlossaryReader,
|
|
3038
|
+
htmlDlReader,
|
|
3039
|
+
htmlDfnReader
|
|
3040
|
+
];
|
|
3041
|
+
|
|
3042
|
+
// src/term/core/readers/body.ts
|
|
3043
|
+
function textStart(line) {
|
|
3044
|
+
return line.start + (line.text.length - line.text.trimStart().length);
|
|
3045
|
+
}
|
|
3046
|
+
function isBlank(line) {
|
|
3047
|
+
return line.text.trim() === "";
|
|
3048
|
+
}
|
|
3049
|
+
function nextNonBlank(lines, from) {
|
|
3050
|
+
let k = from;
|
|
3051
|
+
for (let line = lines[k]; line !== void 0 && !line.code && isBlank(line); line = lines[k]) k++;
|
|
3052
|
+
return k;
|
|
3053
|
+
}
|
|
3054
|
+
function bodyLines(input) {
|
|
3055
|
+
const { content } = input;
|
|
3056
|
+
const bodyStart = locateFrontmatter(content)?.closeEnd ?? 0;
|
|
3057
|
+
const body = content.slice(bodyStart);
|
|
3058
|
+
const code = codeRegions(body, input.format, { spans: false });
|
|
3059
|
+
let line = 1;
|
|
3060
|
+
for (let i = 0; i < bodyStart; i++) {
|
|
3061
|
+
if (content.charCodeAt(i) === 10) line++;
|
|
3062
|
+
}
|
|
3063
|
+
const out = [];
|
|
3064
|
+
let pos = bodyStart;
|
|
3065
|
+
while (pos < content.length) {
|
|
3066
|
+
const nl = content.indexOf("\n", pos);
|
|
3067
|
+
const stop = nl === -1 ? content.length : nl;
|
|
3068
|
+
const end = stop > pos && content.charCodeAt(stop - 1) === 13 ? stop - 1 : stop;
|
|
3069
|
+
out.push({
|
|
3070
|
+
text: content.slice(pos, end),
|
|
3071
|
+
start: pos,
|
|
3072
|
+
end,
|
|
3073
|
+
line,
|
|
3074
|
+
code: codeEndAt(code, pos - bodyStart) !== void 0
|
|
3075
|
+
});
|
|
3076
|
+
line++;
|
|
3077
|
+
pos = stop + 1;
|
|
3078
|
+
}
|
|
3079
|
+
return out;
|
|
3080
|
+
}
|
|
3081
|
+
function definitionText(paragraphs) {
|
|
3082
|
+
const text2 = paragraphs.map(
|
|
3083
|
+
(lines) => lines.map((l) => l.trim()).filter((l) => l !== "").join(" ")
|
|
3084
|
+
).filter((p) => p !== "").join("\n\n");
|
|
3085
|
+
return text2 === "" ? void 0 : text2;
|
|
3086
|
+
}
|
|
3087
|
+
function termOf2(input, construct, entry) {
|
|
3088
|
+
const [first, ...rest] = entry.labels;
|
|
3089
|
+
const label = first?.text.trim() ?? "";
|
|
3090
|
+
const record = { label };
|
|
3091
|
+
const fieldLines = { label: first?.line ?? entry.definitionLine };
|
|
3092
|
+
const alts = rest.map((l) => l.text.trim()).filter((t) => t !== "");
|
|
3093
|
+
const [firstAlt] = rest;
|
|
3094
|
+
if (alts.length > 0 && firstAlt !== void 0) {
|
|
3095
|
+
record["alt-labels"] = alts;
|
|
3096
|
+
fieldLines["alt-labels"] = firstAlt.line;
|
|
3097
|
+
}
|
|
3098
|
+
record.definition = entry.definition;
|
|
3099
|
+
fieldLines.definition = entry.definitionLine;
|
|
3100
|
+
const language = input.metadata["language"];
|
|
3101
|
+
return {
|
|
3102
|
+
id: slugOf(label),
|
|
3103
|
+
record,
|
|
3104
|
+
...typeof language === "string" && language.trim() !== "" ? { language: language.trim() } : {},
|
|
3105
|
+
location: {
|
|
3106
|
+
file: input.file,
|
|
3107
|
+
...input.path === void 0 ? {} : { path: input.path },
|
|
3108
|
+
construct,
|
|
3109
|
+
line: first?.line ?? entry.definitionLine,
|
|
3110
|
+
fieldLines,
|
|
3111
|
+
span: { start: entry.start, end: entry.end }
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
function skippedNotice(input, line, label, missing) {
|
|
3116
|
+
return `${input.file}:${String(line)}: skipped a ${label} entry with no ${missing}.`;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// src/term/core/readers/asciidoc-glossary.ts
|
|
3120
|
+
var LABEL4 = "[glossary] list";
|
|
3121
|
+
var ATTRIBUTE = /^\[glossary\][ \t]*$/;
|
|
3122
|
+
var ITEM = /^[ \t]*(.*?[^:\s])::(?:[ \t]+(.*))?$/;
|
|
3123
|
+
var BLOCK_TITLE = /^\.[^.\s]/;
|
|
3124
|
+
var LIST_END = /^(?:=+[ \t]|\[.*\][ \t]*$|(?:-{4,}|={4,}|\*{4,}|\.{4,}|_{4,}|\+{4,}|\|===)[ \t]*$)/;
|
|
3125
|
+
function item(line) {
|
|
3126
|
+
const match = ITEM.exec(line.text);
|
|
3127
|
+
const term = match?.[1];
|
|
3128
|
+
if (match === null || term === void 0) return void 0;
|
|
3129
|
+
const text2 = match[2]?.trim();
|
|
3130
|
+
return { term, text: text2 === "" ? void 0 : text2 };
|
|
3131
|
+
}
|
|
3132
|
+
function read4(input) {
|
|
3133
|
+
const terms = [];
|
|
3134
|
+
const notices = [];
|
|
3135
|
+
const lines = bodyLines(input);
|
|
3136
|
+
let i = 0;
|
|
3137
|
+
while (i < lines.length) {
|
|
3138
|
+
const line = lines[i];
|
|
3139
|
+
i++;
|
|
3140
|
+
if (line === void 0 || line.code || !ATTRIBUTE.test(line.text)) continue;
|
|
3141
|
+
let start2 = i;
|
|
3142
|
+
for (let l = lines[start2]; l !== void 0 && !l.code && (isBlank(l) || BLOCK_TITLE.test(l.text)); l = lines[start2]) {
|
|
3143
|
+
start2++;
|
|
3144
|
+
}
|
|
3145
|
+
const first = lines[start2];
|
|
3146
|
+
if (first === void 0 || first.code || item(first) === void 0) continue;
|
|
3147
|
+
i = readList(input, lines, start2, terms, notices);
|
|
3148
|
+
}
|
|
3149
|
+
return { terms, notices };
|
|
3150
|
+
}
|
|
3151
|
+
function readList(input, lines, at, terms, notices) {
|
|
3152
|
+
let pending = [];
|
|
3153
|
+
let open;
|
|
3154
|
+
const close = () => {
|
|
3155
|
+
if (open !== void 0) {
|
|
3156
|
+
const text2 = definitionText(open.paragraphs);
|
|
3157
|
+
const [firstLabel] = open.labels;
|
|
3158
|
+
if (text2 !== void 0 && firstLabel !== void 0) {
|
|
3159
|
+
terms.push(
|
|
3160
|
+
termOf2(input, "asciidoc-glossary", {
|
|
3161
|
+
labels: open.labels.map((l) => ({ text: itemTerm(l), line: l.line })),
|
|
3162
|
+
definition: text2,
|
|
3163
|
+
definitionLine: open.definitionLine,
|
|
3164
|
+
start: textStart(firstLabel),
|
|
3165
|
+
end: open.last.end
|
|
3166
|
+
})
|
|
3167
|
+
);
|
|
3168
|
+
} else if (firstLabel !== void 0) {
|
|
3169
|
+
notices.push(skippedNotice(input, firstLabel.line, LABEL4, "definition"));
|
|
3170
|
+
}
|
|
3171
|
+
open = void 0;
|
|
3172
|
+
}
|
|
3173
|
+
const [firstPending] = pending;
|
|
3174
|
+
if (firstPending !== void 0) {
|
|
3175
|
+
notices.push(skippedNotice(input, firstPending.line, LABEL4, "definition"));
|
|
3176
|
+
pending = [];
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
let i = at;
|
|
3180
|
+
for (; ; ) {
|
|
3181
|
+
const line = lines[i];
|
|
3182
|
+
if (line === void 0 || line.code) break;
|
|
3183
|
+
if (isBlank(line)) {
|
|
3184
|
+
close();
|
|
3185
|
+
const k = nextNonBlank(lines, i);
|
|
3186
|
+
const next = lines[k];
|
|
3187
|
+
if (next === void 0 || next.code || item(next) === void 0) break;
|
|
3188
|
+
i = k;
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
const entry = item(line);
|
|
3192
|
+
if (entry !== void 0) {
|
|
3193
|
+
if (open !== void 0) close();
|
|
3194
|
+
pending.push(line);
|
|
3195
|
+
if (entry.text !== void 0) {
|
|
3196
|
+
open = { labels: pending, definitionLine: line.line, paragraphs: [[entry.text]], last: line };
|
|
3197
|
+
pending = [];
|
|
3198
|
+
}
|
|
3199
|
+
i++;
|
|
3200
|
+
continue;
|
|
3201
|
+
}
|
|
3202
|
+
if (line.text.trim() === "+" && open !== void 0) {
|
|
3203
|
+
open.paragraphs.push([]);
|
|
3204
|
+
i++;
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
if (LIST_END.test(line.text) || BLOCK_TITLE.test(line.text)) break;
|
|
3208
|
+
if (open !== void 0) {
|
|
3209
|
+
open.paragraphs.at(-1)?.push(line.text);
|
|
3210
|
+
open.last = line;
|
|
3211
|
+
} else if (pending.length > 0) {
|
|
3212
|
+
open = { labels: pending, definitionLine: line.line, paragraphs: [[line.text]], last: line };
|
|
3213
|
+
pending = [];
|
|
3214
|
+
} else {
|
|
3215
|
+
break;
|
|
3216
|
+
}
|
|
3217
|
+
i++;
|
|
3218
|
+
}
|
|
3219
|
+
close();
|
|
3220
|
+
return i;
|
|
3221
|
+
}
|
|
3222
|
+
function itemTerm(line) {
|
|
3223
|
+
return item(line)?.term ?? line.text;
|
|
3224
|
+
}
|
|
3225
|
+
var asciidocGlossaryReader = {
|
|
3226
|
+
construct: "asciidoc-glossary",
|
|
3227
|
+
label: LABEL4,
|
|
3228
|
+
formats: ["asciidoc"],
|
|
3229
|
+
read: read4,
|
|
3230
|
+
apply: (input, terms) => applyEntries(input, read4, terms, { holds: LIST_HOLDS, id: false, serialize: (term) => asciidocEntry(term.record) })
|
|
3231
|
+
};
|
|
3232
|
+
|
|
3233
|
+
// src/term/core/readers/markdown-deflist.ts
|
|
3234
|
+
var LABEL5 = "definition list";
|
|
3235
|
+
var DEFINITION = /^:(?:[ \t]+(.*))?$/;
|
|
3236
|
+
var INDENTED = /^(?: {4}|\t)/;
|
|
3237
|
+
var HEADING = /^#{1,6}(?:[ \t]|$)/;
|
|
3238
|
+
function definitionStart(line) {
|
|
3239
|
+
if (line === void 0 || line.code) return void 0;
|
|
3240
|
+
const match = DEFINITION.exec(line.text);
|
|
3241
|
+
return match === null ? void 0 : match[1] ?? "";
|
|
3242
|
+
}
|
|
3243
|
+
function read5(input) {
|
|
3244
|
+
const terms = [];
|
|
3245
|
+
const notices = [];
|
|
3246
|
+
if (input.metadata["type"] !== "term-set") return { terms, notices };
|
|
3247
|
+
const lines = bodyLines(input);
|
|
3248
|
+
let i = 0;
|
|
3249
|
+
while (i < lines.length) {
|
|
3250
|
+
const first = lines[i];
|
|
3251
|
+
if (first === void 0) break;
|
|
3252
|
+
if (first.code || isBlank(first)) {
|
|
3253
|
+
i++;
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
const labels = [];
|
|
3257
|
+
let j = i;
|
|
3258
|
+
for (; ; ) {
|
|
3259
|
+
const line = lines[j];
|
|
3260
|
+
if (line === void 0 || line.code || isBlank(line) || definitionStart(line) !== void 0) break;
|
|
3261
|
+
labels.push(line);
|
|
3262
|
+
j++;
|
|
3263
|
+
}
|
|
3264
|
+
const opener = definitionStart(lines[j]);
|
|
3265
|
+
if (opener === void 0) {
|
|
3266
|
+
i = j === i ? i + 1 : j;
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
if (labels.length === 0 || labels.some((l) => HEADING.test(l.text))) {
|
|
3270
|
+
const orphan = lines[j];
|
|
3271
|
+
if (orphan !== void 0) notices.push(skippedNotice(input, orphan.line, LABEL5, "term"));
|
|
3272
|
+
i = readDefinitions(lines, j, []);
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3275
|
+
const definitions = [];
|
|
3276
|
+
i = readDefinitions(lines, j, definitions);
|
|
3277
|
+
const firstLabel = labels[0];
|
|
3278
|
+
const [kept] = definitions;
|
|
3279
|
+
if (firstLabel === void 0 || kept === void 0) continue;
|
|
3280
|
+
const text2 = definitionText(kept.paragraphs);
|
|
3281
|
+
if (text2 === void 0) {
|
|
3282
|
+
notices.push(skippedNotice(input, firstLabel.line, LABEL5, "definition"));
|
|
3283
|
+
continue;
|
|
3284
|
+
}
|
|
3285
|
+
for (const extra of definitions.slice(1)) {
|
|
3286
|
+
notices.push(
|
|
3287
|
+
`${input.file}:${String(extra.line)}: kept the first of ${String(definitions.length)} definitions for "${firstLabel.text.trim()}".`
|
|
3288
|
+
);
|
|
3289
|
+
}
|
|
3290
|
+
const last = lastContentLine(lines, i);
|
|
3291
|
+
terms.push(
|
|
3292
|
+
termOf2(input, "markdown-deflist", {
|
|
3293
|
+
labels: labels.map((l) => ({ text: l.text, line: l.line })),
|
|
3294
|
+
definition: text2,
|
|
3295
|
+
definitionLine: kept.line,
|
|
3296
|
+
start: textStart(firstLabel),
|
|
3297
|
+
end: last?.end ?? firstLabel.end
|
|
3298
|
+
})
|
|
3299
|
+
);
|
|
3300
|
+
}
|
|
3301
|
+
return { terms, notices };
|
|
3302
|
+
}
|
|
3303
|
+
function lastContentLine(lines, stop) {
|
|
3304
|
+
for (let k = stop - 1; k >= 0; k--) {
|
|
3305
|
+
const line = lines[k];
|
|
3306
|
+
if (line !== void 0 && !isBlank(line)) return line;
|
|
3307
|
+
}
|
|
3308
|
+
return void 0;
|
|
3309
|
+
}
|
|
3310
|
+
function readDefinitions(lines, at, out) {
|
|
3311
|
+
let i = at;
|
|
3312
|
+
for (; ; ) {
|
|
3313
|
+
const opener = lines[i];
|
|
3314
|
+
const text2 = definitionStart(opener);
|
|
3315
|
+
if (opener === void 0 || text2 === void 0) return i;
|
|
3316
|
+
const definition = { line: opener.line, paragraphs: [[text2]] };
|
|
3317
|
+
out.push(definition);
|
|
3318
|
+
i++;
|
|
3319
|
+
for (; ; ) {
|
|
3320
|
+
const line = lines[i];
|
|
3321
|
+
if (line === void 0 || line.code) return i;
|
|
3322
|
+
if (definitionStart(line) !== void 0) break;
|
|
3323
|
+
if (!isBlank(line)) {
|
|
3324
|
+
definition.paragraphs.at(-1)?.push(line.text.replace(INDENTED, ""));
|
|
3325
|
+
i++;
|
|
3326
|
+
continue;
|
|
3327
|
+
}
|
|
3328
|
+
const k = nextNonBlank(lines, i);
|
|
3329
|
+
const next = lines[k];
|
|
3330
|
+
if (next === void 0 || next.code) return i;
|
|
3331
|
+
if (definitionStart(next) !== void 0) {
|
|
3332
|
+
i = k;
|
|
3333
|
+
break;
|
|
3334
|
+
}
|
|
3335
|
+
if (!INDENTED.test(next.text)) return i;
|
|
3336
|
+
definition.paragraphs.push([]);
|
|
3337
|
+
i = k;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
var markdownDeflistReader = {
|
|
3342
|
+
construct: "markdown-deflist",
|
|
3343
|
+
label: LABEL5,
|
|
3344
|
+
formats: ["markdown", "mdx"],
|
|
3345
|
+
read: read5,
|
|
3346
|
+
apply: (input, terms) => applyEntries(input, read5, terms, { holds: LIST_HOLDS, id: false, serialize: (term) => markdownEntry(term.record) })
|
|
3347
|
+
};
|
|
3348
|
+
|
|
3349
|
+
// src/term/core/readers/rst-glossary.ts
|
|
3350
|
+
var LABEL6 = ".. glossary::";
|
|
3351
|
+
var DIRECTIVE = /^[ \t]*\.\.[ \t]+glossary::[ \t]*$/;
|
|
3352
|
+
var CODE_DIRECTIVE = /^[ \t]*\.\.[ \t]+(?:code-block|code|sourcecode)::/;
|
|
3353
|
+
var EXPLICIT_MARKUP = /^[ \t]*\.\.(?:[ \t]|$)/;
|
|
3354
|
+
var OPTION = /^[ \t]*:[^:\s][^:]*:(?:[ \t].*)?$/;
|
|
3355
|
+
var CLASSIFIER = /[ \t]+:[ \t]+/;
|
|
3356
|
+
function indentOf(text2) {
|
|
3357
|
+
let width = 0;
|
|
3358
|
+
for (const ch of text2) {
|
|
3359
|
+
if (ch === " ") width++;
|
|
3360
|
+
else if (ch === " ") width += 8 - width % 8;
|
|
3361
|
+
else break;
|
|
3362
|
+
}
|
|
3363
|
+
return width;
|
|
3364
|
+
}
|
|
3365
|
+
function opensLiteral(line) {
|
|
3366
|
+
if (CODE_DIRECTIVE.test(line.text)) return true;
|
|
3367
|
+
return !EXPLICIT_MARKUP.test(line.text) && line.text.trimEnd().endsWith("::");
|
|
3368
|
+
}
|
|
3369
|
+
function read6(input) {
|
|
3370
|
+
const terms = [];
|
|
3371
|
+
const notices = [];
|
|
3372
|
+
const lines = bodyLines(input);
|
|
3373
|
+
let i = 0;
|
|
3374
|
+
while (i < lines.length) {
|
|
3375
|
+
const line = lines[i];
|
|
3376
|
+
if (line === void 0) break;
|
|
3377
|
+
if (isBlank(line)) {
|
|
3378
|
+
i++;
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3381
|
+
if (DIRECTIVE.test(line.text)) {
|
|
3382
|
+
i = readGlossary(input, lines, i, terms, notices);
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3385
|
+
i = opensLiteral(line) ? blockEnd(lines, i + 1, indentOf(line.text)) : i + 1;
|
|
3386
|
+
}
|
|
3387
|
+
return { terms, notices };
|
|
3388
|
+
}
|
|
3389
|
+
function blockEnd(lines, from, indent) {
|
|
3390
|
+
let k = from;
|
|
3391
|
+
for (let l = lines[k]; l !== void 0 && (isBlank(l) || indentOf(l.text) > indent); l = lines[k]) k++;
|
|
3392
|
+
return k;
|
|
3393
|
+
}
|
|
3394
|
+
function readGlossary(input, lines, at, terms, notices) {
|
|
3395
|
+
const directive = lines[at];
|
|
3396
|
+
if (directive === void 0) return at + 1;
|
|
3397
|
+
const end = blockEnd(lines, at + 1, indentOf(directive.text));
|
|
3398
|
+
let i = at + 1;
|
|
3399
|
+
for (let l = lines[i]; i < end && l !== void 0 && !isBlank(l) && OPTION.test(l.text); l = lines[i]) i++;
|
|
3400
|
+
let base;
|
|
3401
|
+
let labels = [];
|
|
3402
|
+
let definition;
|
|
3403
|
+
let orphan = false;
|
|
3404
|
+
const close = () => {
|
|
3405
|
+
const [first] = labels;
|
|
3406
|
+
if (first !== void 0) {
|
|
3407
|
+
const text2 = definition === void 0 ? void 0 : definitionText(definition.paragraphs);
|
|
3408
|
+
if (definition !== void 0 && text2 !== void 0) {
|
|
3409
|
+
terms.push(
|
|
3410
|
+
termOf2(input, "rst-glossary", {
|
|
3411
|
+
labels: labels.map((l) => ({ text: termText(l), line: l.line })),
|
|
3412
|
+
definition: text2,
|
|
3413
|
+
definitionLine: definition.line,
|
|
3414
|
+
start: textStart(first),
|
|
3415
|
+
end: definition.last.end
|
|
3416
|
+
})
|
|
3417
|
+
);
|
|
3418
|
+
} else {
|
|
3419
|
+
notices.push(skippedNotice(input, first.line, LABEL6, "definition"));
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
labels = [];
|
|
3423
|
+
definition = void 0;
|
|
3424
|
+
};
|
|
3425
|
+
let blank = false;
|
|
3426
|
+
for (; i < end; i++) {
|
|
3427
|
+
const line = lines[i];
|
|
3428
|
+
if (line === void 0) break;
|
|
3429
|
+
if (isBlank(line)) {
|
|
3430
|
+
if (definition !== void 0) definition.gap = true;
|
|
3431
|
+
blank = true;
|
|
3432
|
+
continue;
|
|
3433
|
+
}
|
|
3434
|
+
const indent = indentOf(line.text);
|
|
3435
|
+
base ??= indent;
|
|
3436
|
+
if (indent <= base) {
|
|
3437
|
+
if (definition !== void 0 || blank) close();
|
|
3438
|
+
orphan = false;
|
|
3439
|
+
blank = false;
|
|
3440
|
+
labels.push(line);
|
|
3441
|
+
continue;
|
|
3442
|
+
}
|
|
3443
|
+
blank = false;
|
|
3444
|
+
if (definition !== void 0) {
|
|
3445
|
+
if (definition.gap) {
|
|
3446
|
+
definition.paragraphs.push([]);
|
|
3447
|
+
definition.gap = false;
|
|
3448
|
+
}
|
|
3449
|
+
definition.paragraphs.at(-1)?.push(line.text);
|
|
3450
|
+
definition.last = line;
|
|
3451
|
+
} else if (labels.length > 0) {
|
|
3452
|
+
definition = { line: line.line, paragraphs: [[line.text]], last: line, gap: false };
|
|
3453
|
+
} else if (!orphan) {
|
|
3454
|
+
notices.push(skippedNotice(input, line.line, LABEL6, "term"));
|
|
3455
|
+
orphan = true;
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
close();
|
|
3459
|
+
return end;
|
|
3460
|
+
}
|
|
3461
|
+
function termText(line) {
|
|
3462
|
+
const text2 = line.text.trim();
|
|
3463
|
+
const match = CLASSIFIER.exec(text2);
|
|
3464
|
+
return match === null ? text2 : text2.slice(0, match.index);
|
|
3465
|
+
}
|
|
3466
|
+
var rstGlossaryReader = {
|
|
3467
|
+
construct: "rst-glossary",
|
|
3468
|
+
label: LABEL6,
|
|
3469
|
+
formats: ["rst"],
|
|
3470
|
+
read: read6,
|
|
3471
|
+
apply: (input, terms) => applyEntries(input, read6, terms, { holds: LIST_HOLDS, id: false, serialize: (term) => rstEntry(term.record) })
|
|
3472
|
+
};
|
|
3473
|
+
|
|
3474
|
+
// src/term/core/readers/text.ts
|
|
3475
|
+
var TEXT_READERS = [
|
|
3476
|
+
markdownDeflistReader,
|
|
3477
|
+
asciidocGlossaryReader,
|
|
3478
|
+
rstGlossaryReader
|
|
3479
|
+
];
|
|
3480
|
+
|
|
3481
|
+
// src/term/core/readers/index.ts
|
|
3482
|
+
var TERM_READERS = [...STRUCTURED_READERS, ...TEXT_READERS];
|
|
3483
|
+
function readersForFormat(format, readers = TERM_READERS) {
|
|
3484
|
+
return readers.filter((reader) => reader.formats.includes(format));
|
|
3485
|
+
}
|
|
3486
|
+
function readerForConstruct(construct, readers = TERM_READERS) {
|
|
3487
|
+
return readers.find((reader) => reader.construct === construct);
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// src/term/core/load-set.ts
|
|
3491
|
+
var MANIFEST_FORMAT = "manifest";
|
|
3492
|
+
var MANIFEST_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
3493
|
+
function manifestInput(content, file, path) {
|
|
3494
|
+
return { content, file, path, format: MANIFEST_FORMAT, metadata: {}, lineFor: () => void 0 };
|
|
3495
|
+
}
|
|
3496
|
+
function labelsOf2(value) {
|
|
3497
|
+
if (typeof value === "string") return value.trim() === "" ? [] : [value];
|
|
3498
|
+
if (Array.isArray(value)) {
|
|
3499
|
+
return value.filter((item2) => typeof item2 === "string" && item2.trim() !== "");
|
|
3500
|
+
}
|
|
3501
|
+
return [];
|
|
3502
|
+
}
|
|
3503
|
+
function referencesOf(input) {
|
|
3504
|
+
const references2 = [];
|
|
3505
|
+
const add = (value, pointer) => {
|
|
3506
|
+
for (const label of labelsOf2(value)) {
|
|
3507
|
+
const line = input.lineFor(pointer);
|
|
3508
|
+
references2.push({ file: input.file, label, ...line === void 0 ? {} : { line } });
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
add(input.metadata["concepts"], "/concepts");
|
|
3512
|
+
const kg = input.metadata["kg"];
|
|
3513
|
+
if (typeof kg === "object" && kg !== null && !Array.isArray(kg)) {
|
|
3514
|
+
add(kg["concepts"], "/kg/concepts");
|
|
3515
|
+
}
|
|
3516
|
+
return references2;
|
|
3517
|
+
}
|
|
3518
|
+
function read7(input, readers, set) {
|
|
3519
|
+
for (const reader of readersForFormat(input.format, readers)) {
|
|
3520
|
+
const result = reader.read(input);
|
|
3521
|
+
set.terms.push(...result.terms);
|
|
3522
|
+
set.notices.push(...result.notices);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
function extractInput(content, file, path, format) {
|
|
3526
|
+
const extractor = extractorByName(format);
|
|
3527
|
+
if (extractor === void 0) {
|
|
3528
|
+
throw new TermError(`${file}: no reader for format "${format}".`);
|
|
3529
|
+
}
|
|
3530
|
+
let extracted;
|
|
3531
|
+
try {
|
|
3532
|
+
extracted = extractor.extract(content, path ?? file);
|
|
3533
|
+
} catch (error) {
|
|
3534
|
+
throw new TermError(`${file}: could not be read as ${format}: ${errorMessage(error)}`);
|
|
3535
|
+
}
|
|
3536
|
+
return {
|
|
3537
|
+
content,
|
|
3538
|
+
file,
|
|
3539
|
+
...path === void 0 ? {} : { path },
|
|
3540
|
+
format: extractor.name,
|
|
3541
|
+
metadata: extracted.data,
|
|
3542
|
+
lineFor: extracted.lineFor
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
async function loadTermSet(opts) {
|
|
3546
|
+
const { run } = opts;
|
|
3547
|
+
const readers = opts.readers ?? TERM_READERS;
|
|
3548
|
+
const exclude = opts.exclude ?? [];
|
|
3549
|
+
const allowEmpty = opts.allowEmpty ?? run.config?.allowEmpty === true;
|
|
3550
|
+
const usingStdin = run.inputs.includes(STDIN_TOKEN);
|
|
3551
|
+
const inputs = run.inputs.filter((input) => input !== STDIN_TOKEN);
|
|
3552
|
+
if (usingStdin && (opts.as === void 0 || opts.as === "")) {
|
|
3553
|
+
throw new TermError("reading stdin needs --as <format>.");
|
|
3554
|
+
}
|
|
3555
|
+
if (inputs.length === 0 && !usingStdin && run.manifests.length === 0) {
|
|
3556
|
+
throw new TermError(
|
|
3557
|
+
"No files to read. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
3558
|
+
);
|
|
3559
|
+
}
|
|
3560
|
+
const set = { terms: [], references: [], notices: [] };
|
|
3561
|
+
if (inputs.length > 0) {
|
|
3562
|
+
const { files, gitignoreSkipped, named } = await resolveTargetSet({
|
|
3563
|
+
inputs,
|
|
3564
|
+
cwd: run.base,
|
|
3565
|
+
exclude,
|
|
3566
|
+
...opts.exts === void 0 ? {} : { exts: opts.exts },
|
|
3567
|
+
allowEmpty,
|
|
3568
|
+
...gitignoreOptions({
|
|
3569
|
+
...opts.noGitignore === true ? { flag: false } : {},
|
|
3570
|
+
...run.config?.respectGitignore === void 0 ? {} : { configured: run.config.respectGitignore },
|
|
3571
|
+
...opts.onNotice ? { onNotice: opts.onNotice } : {}
|
|
3572
|
+
})
|
|
3573
|
+
});
|
|
3574
|
+
assertNonEmpty({
|
|
3575
|
+
files,
|
|
3576
|
+
inputs,
|
|
3577
|
+
usingStdin,
|
|
3578
|
+
allowEmpty,
|
|
3579
|
+
exclude,
|
|
3580
|
+
...opts.exts === void 0 ? {} : { exts: opts.exts },
|
|
3581
|
+
gitignoreSkipped,
|
|
3582
|
+
action: "read"
|
|
3583
|
+
});
|
|
3584
|
+
const namedFiles = new Set(named);
|
|
3585
|
+
for (const file of files) {
|
|
3586
|
+
const path = resolve2(run.base, file);
|
|
3587
|
+
if (namedFiles.has(file) && MANIFEST_EXTENSIONS.has(extname(file).toLowerCase())) {
|
|
3588
|
+
let content2;
|
|
3589
|
+
try {
|
|
3590
|
+
content2 = await readFile(path, "utf8");
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
throw new TermError(`${file}: could not be read: ${errorMessage(error)}`);
|
|
3593
|
+
}
|
|
3594
|
+
read7(manifestInput(content2, file, path), readers, set);
|
|
3595
|
+
continue;
|
|
3596
|
+
}
|
|
3597
|
+
const extractor = extractorForExtension(extname(file));
|
|
3598
|
+
if (extractor === void 0) {
|
|
3599
|
+
throw new TermError(`${file}: no reader for its extension. Pass --as <format> through stdin to read it.`);
|
|
3600
|
+
}
|
|
3601
|
+
let content;
|
|
3602
|
+
try {
|
|
3603
|
+
content = await readFile(path, "utf8");
|
|
3604
|
+
} catch (error) {
|
|
3605
|
+
throw new TermError(`${file}: could not be read: ${errorMessage(error)}`);
|
|
3606
|
+
}
|
|
3607
|
+
const input = extractInput(content, file, path, extractor.name);
|
|
3608
|
+
set.references.push(...referencesOf(input));
|
|
3609
|
+
read7(input, readers, set);
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
if (usingStdin && opts.as !== void 0) {
|
|
3613
|
+
const input = extractInput(opts.stdin ?? "", STDIN_LABEL, void 0, opts.as);
|
|
3614
|
+
set.references.push(...referencesOf(input));
|
|
3615
|
+
read7(input, readers, set);
|
|
3616
|
+
}
|
|
3617
|
+
for (const { path, written } of run.manifests) {
|
|
3618
|
+
let content;
|
|
3619
|
+
try {
|
|
3620
|
+
content = await readFile(path, "utf8");
|
|
3621
|
+
} catch {
|
|
3622
|
+
const source = run.configSource ?? "manni.config.yaml";
|
|
3623
|
+
throw new TermError(`${source}: term.manifests "${written}" does not exist.`);
|
|
3624
|
+
}
|
|
3625
|
+
read7(manifestInput(content, path, path), readers, set);
|
|
3626
|
+
}
|
|
3627
|
+
if (set.terms.length === 0 && !allowEmpty) {
|
|
3628
|
+
throw new TermError(
|
|
3629
|
+
"no terms found. A term is a page declaring type: term, or an entry in a file declaring type: term-set."
|
|
3630
|
+
);
|
|
3631
|
+
}
|
|
3632
|
+
return set;
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
// src/term/commands/run.ts
|
|
3636
|
+
async function loadTerms(opts) {
|
|
3637
|
+
const cwd = resolve3(opts.cwd ?? process.cwd());
|
|
3638
|
+
const run = await resolveTermRun({
|
|
3639
|
+
cwd,
|
|
3640
|
+
inputs: opts.inputs,
|
|
3641
|
+
...opts.configPath === void 0 ? {} : { configPath: opts.configPath },
|
|
3642
|
+
...opts.collection === void 0 ? {} : { collection: opts.collection }
|
|
3643
|
+
});
|
|
3644
|
+
const set = await loadSet(run, opts);
|
|
3645
|
+
return { cwd, run, set };
|
|
3646
|
+
}
|
|
3647
|
+
async function loadSet(run, opts) {
|
|
3648
|
+
const exclude = [
|
|
3649
|
+
.../* @__PURE__ */ new Set([
|
|
3650
|
+
...opts.exclude ?? [],
|
|
3651
|
+
...run.fromCollections ? run.collections.flatMap((c) => c.exclude) : []
|
|
3652
|
+
])
|
|
3653
|
+
];
|
|
3654
|
+
const set = await loadTermSet({
|
|
3655
|
+
run,
|
|
3656
|
+
exclude,
|
|
3657
|
+
...opts.exts === void 0 ? {} : { exts: opts.exts },
|
|
3658
|
+
...opts.allowEmpty === true ? { allowEmpty: true } : {},
|
|
3659
|
+
...opts.noGitignore === true ? { noGitignore: true } : {},
|
|
3660
|
+
...opts.stdin === void 0 ? {} : { stdin: opts.stdin },
|
|
3661
|
+
...opts.as === void 0 ? {} : { as: opts.as },
|
|
3662
|
+
...opts.onNotice === void 0 ? {} : { onNotice: opts.onNotice },
|
|
3663
|
+
...opts.readers === void 0 ? {} : { readers: opts.readers }
|
|
3664
|
+
});
|
|
3665
|
+
for (const message of set.notices) opts.onNotice?.(message);
|
|
3666
|
+
return set;
|
|
3667
|
+
}
|
|
3668
|
+
function formatChoice(value, allowed) {
|
|
3669
|
+
const found = allowed.find((candidate) => candidate === value);
|
|
3670
|
+
if (found === void 0) {
|
|
3671
|
+
throw new TermError(`unknown format "${value}". Expected ${allowed.join(" | ")}.`);
|
|
3672
|
+
}
|
|
3673
|
+
return found;
|
|
3674
|
+
}
|
|
3675
|
+
function displayPath(path, cwd) {
|
|
3676
|
+
if (!isAbsolute(path)) return path;
|
|
3677
|
+
const rel = relative(cwd, path);
|
|
3678
|
+
return (rel === "" ? "." : rel).replaceAll("\\", "/");
|
|
3679
|
+
}
|
|
3680
|
+
function valeConfigFor(run) {
|
|
3681
|
+
if (run.configDir === void 0) return void 0;
|
|
3682
|
+
const path = valeConfigPath(run.tools, run.configDir);
|
|
3683
|
+
if (path !== void 0 && !existsSync(path)) {
|
|
3684
|
+
const source = run.configSource ?? "manni.config.yaml";
|
|
3685
|
+
throw new TermError(`${source}: tools.vale.config "${run.tools.vale?.config ?? path}" does not exist.`);
|
|
3686
|
+
}
|
|
3687
|
+
return path;
|
|
3688
|
+
}
|
|
3689
|
+
function plural3(n, one, many = `${one}s`) {
|
|
3690
|
+
return `${String(n)} ${n === 1 ? one : many}`;
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
// src/term/commands/check.ts
|
|
3694
|
+
async function runCheck3(opts) {
|
|
3695
|
+
const { cwd, run, set } = await loadTerms(opts);
|
|
3696
|
+
const config = run.config;
|
|
3697
|
+
const findings = checkTermSet(set, {
|
|
3698
|
+
...config?.severity === void 0 ? {} : { severity: config.severity },
|
|
3699
|
+
...config?.abstractMaxLength === void 0 ? {} : { abstractMaxLength: config.abstractMaxLength }
|
|
3700
|
+
});
|
|
3701
|
+
const { results, origin } = toResults(set, findings);
|
|
3702
|
+
const frame = { cwd, base: run.configDir ?? cwd, runBase: run.base };
|
|
3703
|
+
let request = resolveBaselineRequest(
|
|
3704
|
+
opts.baseline === true ? { baseline: true } : {},
|
|
3705
|
+
config?.baseline,
|
|
3706
|
+
run.configDir,
|
|
3707
|
+
cwd,
|
|
3708
|
+
{ current: DEFAULT_TERM_BASELINE_PATH }
|
|
3709
|
+
);
|
|
3710
|
+
if (request !== null && !existsSync2(request.absPath)) {
|
|
3711
|
+
if (opts.baseline !== true) {
|
|
3712
|
+
throw new TermError(
|
|
3713
|
+
`Baseline "${request.label}" not found. Record one with \`manni term check --baseline\`.`
|
|
3714
|
+
);
|
|
3715
|
+
}
|
|
3716
|
+
request = { ...request, write: true };
|
|
3717
|
+
}
|
|
3718
|
+
const { results: reported, baseline } = await settleBaseline(results, request, frame);
|
|
3719
|
+
return {
|
|
3720
|
+
findings: reportedFindings(reported, origin),
|
|
3721
|
+
results: reported,
|
|
3722
|
+
summary: summarize2(reported, baseline),
|
|
3723
|
+
frame,
|
|
3724
|
+
terms: set.terms.length,
|
|
3725
|
+
references: set.references.length,
|
|
3726
|
+
...baseline === void 0 ? {} : { baseline },
|
|
3727
|
+
found: findings.length,
|
|
3728
|
+
cwd
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
// src/term/core/writers/documents.ts
|
|
3733
|
+
import { join } from "node:path";
|
|
3734
|
+
|
|
3735
|
+
// src/term/core/writers/page.ts
|
|
3736
|
+
import { parse as parseYaml, stringify as stringifyYaml2 } from "yaml";
|
|
3737
|
+
function pageMetadata(term) {
|
|
3738
|
+
const { record } = term;
|
|
3739
|
+
const metadata = { title: record.label };
|
|
3740
|
+
if (record.abstract !== void 0) metadata["description"] = record.abstract;
|
|
3741
|
+
metadata["type"] = "term";
|
|
3742
|
+
metadata["id"] = term.id;
|
|
3743
|
+
if (term.language !== void 0) metadata["language"] = term.language;
|
|
3744
|
+
for (const field of TERM_FIELDS) {
|
|
3745
|
+
const value = record[field];
|
|
3746
|
+
if (value !== void 0) metadata[field] = value;
|
|
3747
|
+
}
|
|
3748
|
+
return metadata;
|
|
3749
|
+
}
|
|
3750
|
+
function metadataPage(format, term) {
|
|
3751
|
+
const apply2 = extractorByName(format)?.apply;
|
|
3752
|
+
if (apply2 === void 0) throw new TermError(`manni cannot write ${format} metadata.`);
|
|
3753
|
+
return apply2("---\n---\n", pageMetadata(term));
|
|
3754
|
+
}
|
|
3755
|
+
function yamlFrontmatter(metadata) {
|
|
3756
|
+
return `---
|
|
3757
|
+
${stringifyYaml2(metadata, { lineWidth: 0 })}---
|
|
3758
|
+
`;
|
|
3759
|
+
}
|
|
3760
|
+
function metaContent(value) {
|
|
3761
|
+
if (typeof value === "string" && !value.includes("\n")) {
|
|
3762
|
+
try {
|
|
3763
|
+
if (parseYaml(value) === value) return value;
|
|
3764
|
+
} catch {
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
return JSON.stringify(value);
|
|
3768
|
+
}
|
|
3769
|
+
function htmlPage(term) {
|
|
3770
|
+
const metadata = pageMetadata(term);
|
|
3771
|
+
const metas = Object.entries(metadata).filter(([key]) => key !== "title").map(([key, value]) => ` <meta name="${xmlAttribute(key)}" content="${xmlAttribute(metaContent(value))}">`);
|
|
3772
|
+
return fileOf([
|
|
3773
|
+
"<!DOCTYPE html>",
|
|
3774
|
+
"<html>",
|
|
3775
|
+
" <head>",
|
|
3776
|
+
` <title>${xmlText(term.record.label)}</title>`,
|
|
3777
|
+
...metas,
|
|
3778
|
+
" </head>",
|
|
3779
|
+
" <body></body>",
|
|
3780
|
+
"</html>"
|
|
3781
|
+
]);
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
// src/term/core/writers/render-util.ts
|
|
3785
|
+
var DEFAULT_LANGUAGE = "en";
|
|
3786
|
+
function droppedFields(terms, held) {
|
|
3787
|
+
const dropped = [];
|
|
3788
|
+
for (const term of terms) {
|
|
3789
|
+
for (const field of TERM_FIELDS) {
|
|
3790
|
+
if (term.record[field] !== void 0 && !held.includes(field)) dropped.push({ id: term.id, field });
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
return dropped;
|
|
3794
|
+
}
|
|
3795
|
+
function requireShape(format, target, shape) {
|
|
3796
|
+
if (target.shape === shape) return;
|
|
3797
|
+
throw new TermError(
|
|
3798
|
+
shape === "file" ? `-f ${format} writes one file, not a directory. Pass -o <file>.` : `-f ${format} writes a styles directory, not a file. Pass -o <styles directory>.`
|
|
3799
|
+
);
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
// src/term/core/writers/documents.ts
|
|
3803
|
+
var UNSAFE_NAME = /[/\\:*?"<>|]/;
|
|
3804
|
+
function pagePaths(terms, target, extension) {
|
|
3805
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3806
|
+
return terms.map((term) => {
|
|
3807
|
+
if (UNSAFE_NAME.test(term.id) || term.id === "." || term.id === "..") {
|
|
3808
|
+
throw new TermError(`the id "${term.id}" cannot name a file. Give the term an id without path characters.`);
|
|
3809
|
+
}
|
|
3810
|
+
if (seen.has(term.id)) {
|
|
3811
|
+
throw new TermError(`two terms have the id "${term.id}", and a directory holds one file per id.`);
|
|
3812
|
+
}
|
|
3813
|
+
seen.add(term.id);
|
|
3814
|
+
return join(target.path, `${term.id}${extension}`);
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
function documentWriter(spec) {
|
|
3818
|
+
const { directory } = spec;
|
|
3819
|
+
return {
|
|
3820
|
+
format: spec.format,
|
|
3821
|
+
shapes: directory === void 0 ? ["file"] : ["file", "directory"],
|
|
3822
|
+
holds: (shape) => shape === "directory" && directory !== void 0 ? directory.holds : spec.file.holds,
|
|
3823
|
+
render(terms, target) {
|
|
3824
|
+
if (target.shape === "directory" && directory !== void 0) {
|
|
3825
|
+
const paths = pagePaths(terms, target, spec.extension);
|
|
3826
|
+
return {
|
|
3827
|
+
files: terms.map((term, i) => ({ path: paths[i] ?? "", content: directory.render(term) })),
|
|
3828
|
+
removals: [],
|
|
3829
|
+
dropped: droppedFields(terms, directory.holds),
|
|
3830
|
+
skipped: []
|
|
3831
|
+
};
|
|
3832
|
+
}
|
|
3833
|
+
requireShape(spec.format, target, "file");
|
|
3834
|
+
const written = spec.file.needsDefinition ? terms.filter((t) => t.record.definition !== void 0) : terms;
|
|
3835
|
+
return {
|
|
3836
|
+
files: [{ path: target.path, content: spec.file.render(written) }],
|
|
3837
|
+
removals: [],
|
|
3838
|
+
dropped: droppedFields(written, spec.file.holds),
|
|
3839
|
+
skipped: terms.filter((t) => !written.includes(t)).map((t) => t.id)
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
function entriesBlock(entries, indent = "") {
|
|
3845
|
+
return entries.flatMap((entry, i) => [
|
|
3846
|
+
...i === 0 ? [] : [""],
|
|
3847
|
+
`${indent}${reindent(entry, indent, "\n")}`
|
|
3848
|
+
]);
|
|
3849
|
+
}
|
|
3850
|
+
function markupBlock(entries, indent) {
|
|
3851
|
+
return entries.map((entry) => `${indent}${reindent(entry, indent, "\n")}`);
|
|
3852
|
+
}
|
|
3853
|
+
function textPage(format) {
|
|
3854
|
+
return (term) => metadataPage(format, term);
|
|
3855
|
+
}
|
|
3856
|
+
function listFrontmatter(terms, type) {
|
|
3857
|
+
const metadata = {};
|
|
3858
|
+
if (type) metadata["type"] = "term-set";
|
|
3859
|
+
const language = sharedLanguage(terms);
|
|
3860
|
+
if (language !== void 0) metadata["language"] = language;
|
|
3861
|
+
if (Object.keys(metadata).length === 0) return [];
|
|
3862
|
+
return [yamlFrontmatter(metadata).replace(/\n$/, ""), ""];
|
|
3863
|
+
}
|
|
3864
|
+
function markdownList(terms) {
|
|
3865
|
+
return fileOf([...listFrontmatter(terms, true), ...entriesBlock(terms.map((t) => markdownEntry(t.record)))]);
|
|
3866
|
+
}
|
|
3867
|
+
var XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
3868
|
+
function langAttribute(language) {
|
|
3869
|
+
return language === void 0 ? "" : ` xml:lang="${xmlAttribute(language)}"`;
|
|
3870
|
+
}
|
|
3871
|
+
var markdownWriter = documentWriter({
|
|
3872
|
+
format: "markdown",
|
|
3873
|
+
extension: ".md",
|
|
3874
|
+
file: { holds: LIST_HOLDS, render: markdownList, needsDefinition: true },
|
|
3875
|
+
directory: { holds: TERM_FIELDS, render: textPage("markdown") }
|
|
3876
|
+
});
|
|
3877
|
+
var mdxWriter = documentWriter({
|
|
3878
|
+
format: "mdx",
|
|
3879
|
+
extension: ".mdx",
|
|
3880
|
+
file: { holds: LIST_HOLDS, render: markdownList, needsDefinition: true },
|
|
3881
|
+
directory: { holds: TERM_FIELDS, render: textPage("mdx") }
|
|
3882
|
+
});
|
|
3883
|
+
var asciidocWriter = documentWriter({
|
|
3884
|
+
format: "asciidoc",
|
|
3885
|
+
extension: ".adoc",
|
|
3886
|
+
file: {
|
|
3887
|
+
holds: LIST_HOLDS,
|
|
3888
|
+
needsDefinition: true,
|
|
3889
|
+
render: (terms) => fileOf([...listFrontmatter(terms, false), "[glossary]", ...entriesBlock(terms.map((t) => asciidocEntry(t.record)))])
|
|
3890
|
+
},
|
|
3891
|
+
directory: { holds: TERM_FIELDS, render: textPage("asciidoc") }
|
|
3892
|
+
});
|
|
3893
|
+
var rstWriter = documentWriter({
|
|
3894
|
+
format: "rst",
|
|
3895
|
+
extension: ".rst",
|
|
3896
|
+
file: {
|
|
3897
|
+
holds: LIST_HOLDS,
|
|
3898
|
+
needsDefinition: true,
|
|
3899
|
+
render: (terms) => fileOf([
|
|
3900
|
+
...listFrontmatter(terms, false),
|
|
3901
|
+
".. glossary::",
|
|
3902
|
+
"",
|
|
3903
|
+
...entriesBlock(
|
|
3904
|
+
terms.map((t) => rstEntry(t.record)),
|
|
3905
|
+
" "
|
|
3906
|
+
)
|
|
3907
|
+
])
|
|
3908
|
+
},
|
|
3909
|
+
directory: { holds: TERM_FIELDS, render: textPage("rst") }
|
|
3910
|
+
});
|
|
3911
|
+
var htmlWriter = documentWriter({
|
|
3912
|
+
format: "html",
|
|
3913
|
+
extension: ".html",
|
|
3914
|
+
file: {
|
|
3915
|
+
holds: LIST_HOLDS,
|
|
3916
|
+
needsDefinition: true,
|
|
3917
|
+
render: (terms) => {
|
|
3918
|
+
const language = sharedLanguage(terms);
|
|
3919
|
+
return fileOf([
|
|
3920
|
+
"<!DOCTYPE html>",
|
|
3921
|
+
"<html>",
|
|
3922
|
+
" <head>",
|
|
3923
|
+
" <title>Glossary</title>",
|
|
3924
|
+
' <meta name="type" content="term-set">',
|
|
3925
|
+
...language === void 0 ? [] : [` <meta name="language" content="${xmlAttribute(language)}">`],
|
|
3926
|
+
" </head>",
|
|
3927
|
+
" <body>",
|
|
3928
|
+
" <dl>",
|
|
3929
|
+
...markupBlock(terms.map(htmlDlEntry), " "),
|
|
3930
|
+
" </dl>",
|
|
3931
|
+
" </body>",
|
|
3932
|
+
"</html>"
|
|
3933
|
+
]);
|
|
3934
|
+
}
|
|
3935
|
+
},
|
|
3936
|
+
directory: { holds: TERM_FIELDS, render: htmlPage }
|
|
3937
|
+
});
|
|
3938
|
+
var ditaWriter = documentWriter({
|
|
3939
|
+
format: "dita",
|
|
3940
|
+
extension: ".dita",
|
|
3941
|
+
file: {
|
|
3942
|
+
holds: DITA_HOLDS,
|
|
3943
|
+
needsDefinition: false,
|
|
3944
|
+
render: (terms) => fileOf([
|
|
3945
|
+
XML_DECLARATION,
|
|
3946
|
+
'<!DOCTYPE glossgroup PUBLIC "-//OASIS//DTD DITA 2.0 Glossary Group//EN" "glossgroup.dtd">',
|
|
3947
|
+
`<glossgroup id="glossary"${langAttribute(sharedLanguage(terms))}>`,
|
|
3948
|
+
" <title>Glossary</title>",
|
|
3949
|
+
...markupBlock(terms.map((t) => ditaEntry(t)), " "),
|
|
3950
|
+
"</glossgroup>"
|
|
3951
|
+
])
|
|
3952
|
+
},
|
|
3953
|
+
directory: {
|
|
3954
|
+
holds: DITA_HOLDS,
|
|
3955
|
+
render: (term) => fileOf([
|
|
3956
|
+
XML_DECLARATION,
|
|
3957
|
+
'<!DOCTYPE glossentry PUBLIC "-//OASIS//DTD DITA 2.0 Glossary Entry//EN" "glossentry.dtd">',
|
|
3958
|
+
ditaEntry(term, term.language)
|
|
3959
|
+
])
|
|
3960
|
+
}
|
|
3961
|
+
});
|
|
3962
|
+
var docbookWriter = documentWriter({
|
|
3963
|
+
format: "docbook",
|
|
3964
|
+
extension: ".xml",
|
|
3965
|
+
file: {
|
|
3966
|
+
holds: DOCBOOK_HOLDS,
|
|
3967
|
+
needsDefinition: false,
|
|
3968
|
+
render: (terms) => fileOf([
|
|
3969
|
+
XML_DECLARATION,
|
|
3970
|
+
`<glossary xmlns="http://docbook.org/ns/docbook" version="5.0"${langAttribute(sharedLanguage(terms))}>`,
|
|
3971
|
+
" <title>Glossary</title>",
|
|
3972
|
+
...markupBlock(terms.map(docbookEntry), " "),
|
|
3973
|
+
"</glossary>"
|
|
3974
|
+
])
|
|
3975
|
+
}
|
|
3976
|
+
});
|
|
3977
|
+
var DOCUMENT_WRITERS = [
|
|
3978
|
+
markdownWriter,
|
|
3979
|
+
mdxWriter,
|
|
3980
|
+
asciidocWriter,
|
|
3981
|
+
rstWriter,
|
|
3982
|
+
htmlWriter,
|
|
3983
|
+
ditaWriter,
|
|
3984
|
+
docbookWriter
|
|
3985
|
+
];
|
|
3986
|
+
|
|
3987
|
+
// src/term/core/writers/csv.ts
|
|
3988
|
+
var LANGUAGE_COLUMNS = [
|
|
3989
|
+
["label", "Term"],
|
|
3990
|
+
["definition", "Definition"],
|
|
3991
|
+
["abstract", "Abstract"],
|
|
3992
|
+
["alt-labels", "Alt labels"],
|
|
3993
|
+
["hidden-labels", "Hidden labels"],
|
|
3994
|
+
["scope-note", "Scope note"]
|
|
3995
|
+
];
|
|
3996
|
+
var NEUTRAL_COLUMNS = [
|
|
3997
|
+
["broader", "Broader"],
|
|
3998
|
+
["narrower", "Narrower"],
|
|
3999
|
+
["related-terms", "Related terms"],
|
|
4000
|
+
["see", "See"]
|
|
4001
|
+
];
|
|
4002
|
+
var LIST_SEPARATOR = " | ";
|
|
4003
|
+
function cell(value) {
|
|
4004
|
+
return /[",\r\n]/.test(value) ? `"${value.replaceAll('"', '""')}"` : value;
|
|
4005
|
+
}
|
|
4006
|
+
function fieldText(term, field) {
|
|
4007
|
+
const value = term.record[field];
|
|
4008
|
+
if (value === void 0) return "";
|
|
4009
|
+
return typeof value === "string" ? value : value.join(LIST_SEPARATOR);
|
|
4010
|
+
}
|
|
4011
|
+
var csvWriter = {
|
|
4012
|
+
format: "csv",
|
|
4013
|
+
shapes: ["file"],
|
|
4014
|
+
holds: (_shape) => TERM_FIELDS,
|
|
4015
|
+
render(terms, target) {
|
|
4016
|
+
requireShape("csv", target, "file");
|
|
4017
|
+
const languages = [];
|
|
4018
|
+
for (const term of terms) {
|
|
4019
|
+
const language = term.language ?? DEFAULT_LANGUAGE;
|
|
4020
|
+
if (!languages.includes(language)) languages.push(language);
|
|
4021
|
+
}
|
|
4022
|
+
const header = [
|
|
4023
|
+
"ID",
|
|
4024
|
+
...languages.flatMap((language) => LANGUAGE_COLUMNS.map(([, name]) => `${name} [${language}]`)),
|
|
4025
|
+
...NEUTRAL_COLUMNS.map(([, name]) => name)
|
|
4026
|
+
];
|
|
4027
|
+
const rows = terms.map((term) => {
|
|
4028
|
+
const own = term.language ?? DEFAULT_LANGUAGE;
|
|
4029
|
+
return [
|
|
4030
|
+
term.id,
|
|
4031
|
+
...languages.flatMap(
|
|
4032
|
+
(language) => LANGUAGE_COLUMNS.map(([field]) => language === own ? fieldText(term, field) : "")
|
|
4033
|
+
),
|
|
4034
|
+
...NEUTRAL_COLUMNS.map(([field]) => fieldText(term, field))
|
|
4035
|
+
];
|
|
4036
|
+
});
|
|
4037
|
+
const content = [header, ...rows].map((row) => `${row.map(cell).join(",")}
|
|
4038
|
+
`).join("");
|
|
4039
|
+
return {
|
|
4040
|
+
files: [{ path: target.path, content }],
|
|
4041
|
+
removals: [],
|
|
4042
|
+
dropped: droppedFields(terms, TERM_FIELDS),
|
|
4043
|
+
skipped: []
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
};
|
|
4047
|
+
|
|
4048
|
+
// src/term/core/writers/json.ts
|
|
4049
|
+
var jsonWriter = {
|
|
4050
|
+
format: "json",
|
|
4051
|
+
shapes: ["file"],
|
|
4052
|
+
holds: (_shape) => TERM_FIELDS,
|
|
4053
|
+
render(terms, target) {
|
|
4054
|
+
requireShape("json", target, "file");
|
|
4055
|
+
const entries = terms.map((term) => {
|
|
4056
|
+
const entry = { id: term.id };
|
|
4057
|
+
if (term.language !== void 0) entry["language"] = term.language;
|
|
4058
|
+
for (const field of TERM_FIELDS) {
|
|
4059
|
+
const value = term.record[field];
|
|
4060
|
+
if (value !== void 0) entry[field] = value;
|
|
4061
|
+
}
|
|
4062
|
+
return entry;
|
|
4063
|
+
});
|
|
4064
|
+
const content = `${JSON.stringify({ terms: entries }, null, 2)}
|
|
4065
|
+
`;
|
|
4066
|
+
return {
|
|
4067
|
+
files: [{ path: target.path, content }],
|
|
4068
|
+
removals: [],
|
|
4069
|
+
dropped: droppedFields(terms, TERM_FIELDS),
|
|
4070
|
+
skipped: []
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
4074
|
+
|
|
4075
|
+
// src/term/core/writers/skos.ts
|
|
4076
|
+
var HOLDS = [
|
|
4077
|
+
"label",
|
|
4078
|
+
"definition",
|
|
4079
|
+
"alt-labels",
|
|
4080
|
+
"hidden-labels",
|
|
4081
|
+
"broader",
|
|
4082
|
+
"narrower",
|
|
4083
|
+
"related-terms",
|
|
4084
|
+
"scope-note"
|
|
4085
|
+
];
|
|
4086
|
+
var CONTEXT = {
|
|
4087
|
+
skos: "http://www.w3.org/2004/02/skos/core#",
|
|
4088
|
+
dcterms: "http://purl.org/dc/terms/"
|
|
4089
|
+
};
|
|
4090
|
+
var SCHEME_ID = "";
|
|
4091
|
+
function literal(term, value) {
|
|
4092
|
+
return term.language === void 0 ? value : { "@value": value, "@language": term.language };
|
|
4093
|
+
}
|
|
4094
|
+
function resolve4(terms, self, value) {
|
|
4095
|
+
const wanted = value.toLowerCase();
|
|
4096
|
+
const others = terms.filter((t) => t !== self);
|
|
4097
|
+
return others.find((t) => t.record.label.toLowerCase() === wanted) ?? others.find((t) => t.id.toLowerCase() === wanted);
|
|
4098
|
+
}
|
|
4099
|
+
function references(terms, self, values) {
|
|
4100
|
+
const ids = [];
|
|
4101
|
+
for (const value of values ?? []) {
|
|
4102
|
+
const target = resolve4(terms, self, value);
|
|
4103
|
+
if (target !== void 0 && !ids.includes(target.id)) ids.push(target.id);
|
|
4104
|
+
}
|
|
4105
|
+
return ids.map((id) => ({ "@id": `#${id}` }));
|
|
4106
|
+
}
|
|
4107
|
+
function concept(terms, term) {
|
|
4108
|
+
const { record } = term;
|
|
4109
|
+
const node = {
|
|
4110
|
+
"@id": `#${term.id}`,
|
|
4111
|
+
"@type": "skos:Concept",
|
|
4112
|
+
"skos:inScheme": { "@id": SCHEME_ID },
|
|
4113
|
+
"skos:prefLabel": literal(term, record.label)
|
|
4114
|
+
};
|
|
4115
|
+
if (record["alt-labels"] !== void 0) node["skos:altLabel"] = record["alt-labels"].map((v) => literal(term, v));
|
|
4116
|
+
if (record["hidden-labels"] !== void 0) {
|
|
4117
|
+
node["skos:hiddenLabel"] = record["hidden-labels"].map((v) => literal(term, v));
|
|
4118
|
+
}
|
|
4119
|
+
if (record.definition !== void 0) node["skos:definition"] = literal(term, record.definition);
|
|
4120
|
+
if (record["scope-note"] !== void 0) node["skos:scopeNote"] = literal(term, record["scope-note"]);
|
|
4121
|
+
const relations = [
|
|
4122
|
+
["skos:broader", record.broader],
|
|
4123
|
+
["skos:narrower", record.narrower],
|
|
4124
|
+
["skos:related", record["related-terms"]]
|
|
4125
|
+
];
|
|
4126
|
+
for (const [key, values] of relations) {
|
|
4127
|
+
const refs = references(terms, term, values);
|
|
4128
|
+
if (refs.length > 0) node[key] = refs;
|
|
4129
|
+
}
|
|
4130
|
+
return node;
|
|
4131
|
+
}
|
|
4132
|
+
var skosWriter = {
|
|
4133
|
+
format: "skos",
|
|
4134
|
+
shapes: ["file"],
|
|
4135
|
+
holds: (_shape) => HOLDS,
|
|
4136
|
+
render(terms, target) {
|
|
4137
|
+
requireShape("skos", target, "file");
|
|
4138
|
+
const document = {
|
|
4139
|
+
"@context": CONTEXT,
|
|
4140
|
+
"@graph": [
|
|
4141
|
+
{
|
|
4142
|
+
"@id": SCHEME_ID,
|
|
4143
|
+
"@type": "skos:ConceptScheme",
|
|
4144
|
+
"dcterms:description": "Generated by manni term write."
|
|
4145
|
+
},
|
|
4146
|
+
...terms.map((term) => concept(terms, term))
|
|
4147
|
+
]
|
|
4148
|
+
};
|
|
4149
|
+
const content = `${JSON.stringify(document, null, 2)}
|
|
4150
|
+
`;
|
|
4151
|
+
return {
|
|
4152
|
+
files: [{ path: target.path, content }],
|
|
4153
|
+
removals: [],
|
|
4154
|
+
dropped: droppedFields(terms, HOLDS),
|
|
4155
|
+
skipped: []
|
|
4156
|
+
};
|
|
4157
|
+
}
|
|
4158
|
+
};
|
|
4159
|
+
|
|
4160
|
+
// src/term/core/writers/tbx.ts
|
|
4161
|
+
var HOLDS2 = ["label", "definition", "alt-labels", "hidden-labels"];
|
|
4162
|
+
function text(value) {
|
|
4163
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
4164
|
+
}
|
|
4165
|
+
function attribute(value) {
|
|
4166
|
+
return text(value).replaceAll('"', """);
|
|
4167
|
+
}
|
|
4168
|
+
function tig(designation, status) {
|
|
4169
|
+
return [
|
|
4170
|
+
" <tig>",
|
|
4171
|
+
` <term>${text(designation)}</term>`,
|
|
4172
|
+
` <termNote type="administrativeStatus">${status}</termNote>`,
|
|
4173
|
+
" </tig>"
|
|
4174
|
+
];
|
|
4175
|
+
}
|
|
4176
|
+
function termEntry(term) {
|
|
4177
|
+
const { record } = term;
|
|
4178
|
+
return [
|
|
4179
|
+
` <termEntry id="${attribute(term.id)}">`,
|
|
4180
|
+
...record.definition === void 0 ? [] : [` <descrip type="definition">${text(record.definition)}</descrip>`],
|
|
4181
|
+
` <langSet xml:lang="${attribute(term.language ?? DEFAULT_LANGUAGE)}">`,
|
|
4182
|
+
...tig(record.label, "preferredTerm-admn-sts"),
|
|
4183
|
+
...(record["alt-labels"] ?? []).flatMap((label) => tig(label, "admittedTerm-admn-sts")),
|
|
4184
|
+
...(record["hidden-labels"] ?? []).flatMap((label) => tig(label, "deprecatedTerm-admn-sts")),
|
|
4185
|
+
" </langSet>",
|
|
4186
|
+
" </termEntry>"
|
|
4187
|
+
];
|
|
4188
|
+
}
|
|
4189
|
+
var tbxWriter = {
|
|
4190
|
+
format: "tbx",
|
|
4191
|
+
shapes: ["file"],
|
|
4192
|
+
holds: (_shape) => HOLDS2,
|
|
4193
|
+
render(terms, target) {
|
|
4194
|
+
requireShape("tbx", target, "file");
|
|
4195
|
+
const language = terms[0]?.language ?? DEFAULT_LANGUAGE;
|
|
4196
|
+
const lines = [
|
|
4197
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
4198
|
+
`<martif type="TBX" xml:lang="${attribute(language)}">`,
|
|
4199
|
+
" <martifHeader>",
|
|
4200
|
+
" <fileDesc>",
|
|
4201
|
+
" <sourceDesc>",
|
|
4202
|
+
" <p>manni term write</p>",
|
|
4203
|
+
" </sourceDesc>",
|
|
4204
|
+
" </fileDesc>",
|
|
4205
|
+
" </martifHeader>",
|
|
4206
|
+
" <text>",
|
|
4207
|
+
" <body>",
|
|
4208
|
+
...terms.flatMap(termEntry),
|
|
4209
|
+
" </body>",
|
|
4210
|
+
" </text>",
|
|
4211
|
+
"</martif>"
|
|
4212
|
+
];
|
|
4213
|
+
const content = lines.map((line) => `${line}
|
|
4214
|
+
`).join("");
|
|
4215
|
+
return {
|
|
4216
|
+
files: [{ path: target.path, content }],
|
|
4217
|
+
removals: [],
|
|
4218
|
+
dropped: droppedFields(terms, HOLDS2),
|
|
4219
|
+
skipped: []
|
|
4220
|
+
};
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
4223
|
+
|
|
4224
|
+
// src/term/core/writers/vale.ts
|
|
4225
|
+
import { isAbsolute as isAbsolute2, join as join2, relative as relative2, sep } from "node:path";
|
|
4226
|
+
import { stringify } from "yaml";
|
|
4227
|
+
var VALE_MARKER = "# Generated by manni term write. Edit the terms, not this file.";
|
|
4228
|
+
var STYLE = "Terms";
|
|
4229
|
+
var HOLDS3 = ["label", "alt-labels", "hidden-labels"];
|
|
4230
|
+
var OWN_FILES = ["Casing", "Lowercase", "SentenceStart", "Deprecated"];
|
|
4231
|
+
var REGEX_METACHARACTERS = /[\\^$.|?*+()[\]{}]/g;
|
|
4232
|
+
function escapeRegex(value) {
|
|
4233
|
+
return value.replace(REGEX_METACHARACTERS, "\\$&");
|
|
4234
|
+
}
|
|
4235
|
+
function swapValue(value) {
|
|
4236
|
+
return value.search(REGEX_METACHARACTERS) === -1 ? value : escapeRegex(value);
|
|
4237
|
+
}
|
|
4238
|
+
function scalar(value) {
|
|
4239
|
+
if (/[\r\n]/.test(value)) return JSON.stringify(value);
|
|
4240
|
+
const out = stringify(value, { version: "1.1", lineWidth: 0 });
|
|
4241
|
+
return out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
4242
|
+
}
|
|
4243
|
+
function doubleQuoted(value) {
|
|
4244
|
+
const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll("\r", "\\r");
|
|
4245
|
+
return `"${escaped}"`;
|
|
4246
|
+
}
|
|
4247
|
+
function singleQuoted(value) {
|
|
4248
|
+
return `'${value.replaceAll("'", "''").replaceAll(/[\r\n]+/g, " ")}'`;
|
|
4249
|
+
}
|
|
4250
|
+
function messageText(value) {
|
|
4251
|
+
return value.replaceAll("%", "%%");
|
|
4252
|
+
}
|
|
4253
|
+
function casingOf(value) {
|
|
4254
|
+
if (!/[\p{Lu}\p{Ll}\p{Lt}]/u.test(value)) return "none";
|
|
4255
|
+
const letters = value.match(new RegExp("\\p{L}", "gu"))?.length ?? 0;
|
|
4256
|
+
if (letters >= 2 && !/[\p{Ll}\p{Lt}]/u.test(value)) return "all-caps";
|
|
4257
|
+
if (!/[\p{Lu}\p{Lt}]/u.test(value)) return "lowercase";
|
|
4258
|
+
if (new RegExp("^\\p{Ll}", "u").test(value)) return "lowercase-initial";
|
|
4259
|
+
return "mixed";
|
|
4260
|
+
}
|
|
4261
|
+
function sentenceStartPattern(value) {
|
|
4262
|
+
const [first = "", ...rest] = Array.from(value);
|
|
4263
|
+
const upper = first.toUpperCase();
|
|
4264
|
+
const head = upper !== first && Array.from(upper).length === 1 ? `[${upper}${first}]` : escapeRegex(first);
|
|
4265
|
+
return `${head}${escapeRegex(rest.join(""))}`;
|
|
4266
|
+
}
|
|
4267
|
+
var Swaps = class {
|
|
4268
|
+
entries = /* @__PURE__ */ new Map();
|
|
4269
|
+
add(key, value) {
|
|
4270
|
+
if (!this.entries.has(key)) this.entries.set(key, value);
|
|
4271
|
+
}
|
|
4272
|
+
get size() {
|
|
4273
|
+
return this.entries.size;
|
|
4274
|
+
}
|
|
4275
|
+
lines() {
|
|
4276
|
+
return [...this.entries].map(([key, value]) => ` ${scalar(key)}: ${scalar(value)}`);
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
var ASCII_WORD = /^[A-Za-z0-9_]$/;
|
|
4280
|
+
function bounded(pattern, text2) {
|
|
4281
|
+
const chars = Array.from(text2);
|
|
4282
|
+
const edge = (char) => char !== void 0 && ASCII_WORD.test(char) ? "\\b" : "\\B";
|
|
4283
|
+
return `${edge(chars[0])}${pattern}${edge(chars.at(-1))}`;
|
|
4284
|
+
}
|
|
4285
|
+
function substitution(message, level, options, swaps) {
|
|
4286
|
+
return [
|
|
4287
|
+
VALE_MARKER,
|
|
4288
|
+
"extends: substitution",
|
|
4289
|
+
`message: ${doubleQuoted(message)}`,
|
|
4290
|
+
`level: ${level}`,
|
|
4291
|
+
"ignorecase: true",
|
|
4292
|
+
"nonword: true",
|
|
4293
|
+
...options,
|
|
4294
|
+
"swap:",
|
|
4295
|
+
...swaps.lines()
|
|
4296
|
+
].map((line) => `${line}
|
|
4297
|
+
`).join("");
|
|
4298
|
+
}
|
|
4299
|
+
function acronymRule(acronym, label) {
|
|
4300
|
+
const expansion = escapeRegex(label).replaceAll(/[\r\n]+/g, " ");
|
|
4301
|
+
return [
|
|
4302
|
+
VALE_MARKER,
|
|
4303
|
+
"extends: conditional",
|
|
4304
|
+
`message: ${doubleQuoted(messageText(`Spell out '${acronym}' on first use, as '${label} (${acronym})'.`))}`,
|
|
4305
|
+
"level: warning",
|
|
4306
|
+
"ignorecase: false",
|
|
4307
|
+
`first: ${singleQuoted(`\\b(${escapeRegex(acronym)})\\b`)}`,
|
|
4308
|
+
`second: ${singleQuoted(`(?i)${expansion} \\((${escapeRegex(acronym)})\\)`)}`
|
|
4309
|
+
].map((line) => `${line}
|
|
4310
|
+
`).join("");
|
|
4311
|
+
}
|
|
4312
|
+
function acronymFileName(acronym) {
|
|
4313
|
+
return acronym.replaceAll(/[^\p{L}\p{N}_-]/gu, "-");
|
|
4314
|
+
}
|
|
4315
|
+
function firstLine(content) {
|
|
4316
|
+
const end = content.search(/\r?\n/);
|
|
4317
|
+
return end === -1 ? content : content.slice(0, end);
|
|
4318
|
+
}
|
|
4319
|
+
var valeWriter = {
|
|
4320
|
+
format: "vale",
|
|
4321
|
+
shapes: ["directory"],
|
|
4322
|
+
holds: (_shape) => HOLDS3,
|
|
4323
|
+
render(terms, target, context) {
|
|
4324
|
+
requireShape("vale", target, "directory");
|
|
4325
|
+
const termsDir = join2(target.path, STYLE);
|
|
4326
|
+
const marked = [];
|
|
4327
|
+
for (const [path, content] of context.existing) {
|
|
4328
|
+
const rel = relative2(termsDir, path);
|
|
4329
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) continue;
|
|
4330
|
+
if (firstLine(content) !== VALE_MARKER) {
|
|
4331
|
+
throw new TermError(`${path} was not written by manni. Move it, or pass -o <styles directory>.`);
|
|
4332
|
+
}
|
|
4333
|
+
if (!rel.includes(sep) && !rel.includes("/")) marked.push(path);
|
|
4334
|
+
}
|
|
4335
|
+
const casing = new Swaps();
|
|
4336
|
+
const lowercase = new Swaps();
|
|
4337
|
+
const sentenceStart = new Swaps();
|
|
4338
|
+
const deprecated = new Swaps();
|
|
4339
|
+
const acronyms = [];
|
|
4340
|
+
const labels = new Set(terms.map((t) => t.record.label));
|
|
4341
|
+
for (const term of terms) {
|
|
4342
|
+
const { label } = term.record;
|
|
4343
|
+
const labelIsAllCaps = casingOf(label) === "all-caps";
|
|
4344
|
+
for (const designation of [label, ...term.record["alt-labels"] ?? []]) {
|
|
4345
|
+
const kind = casingOf(designation);
|
|
4346
|
+
if (kind === "lowercase") lowercase.add(bounded(escapeRegex(designation), designation), sentenceStartPattern(designation));
|
|
4347
|
+
if (kind === "lowercase-initial") {
|
|
4348
|
+
sentenceStart.add(bounded(escapeRegex(designation.toLowerCase()), designation), sentenceStartPattern(designation));
|
|
4349
|
+
}
|
|
4350
|
+
if (kind === "mixed") casing.add(bounded(escapeRegex(designation.toLowerCase()), designation), swapValue(designation));
|
|
4351
|
+
if (kind !== "all-caps" || designation === label || labelIsAllCaps || labels.has(designation)) continue;
|
|
4352
|
+
const name = acronymFileName(designation);
|
|
4353
|
+
const own = OWN_FILES.find((file) => file.toLowerCase() === name.toLowerCase());
|
|
4354
|
+
if (own !== void 0) {
|
|
4355
|
+
throw new TermError(`the acronym "${designation}" would replace Terms/${own}.yml. Rename the alt-label.`);
|
|
4356
|
+
}
|
|
4357
|
+
const taken = acronyms.find((a) => a.name.toLowerCase() === name.toLowerCase());
|
|
4358
|
+
if (taken?.acronym === designation) continue;
|
|
4359
|
+
if (taken !== void 0) {
|
|
4360
|
+
throw new TermError(
|
|
4361
|
+
`the acronyms "${taken.acronym}" and "${designation}" would both write Terms/${taken.name}.yml. Rename one.`
|
|
4362
|
+
);
|
|
4363
|
+
}
|
|
4364
|
+
acronyms.push({ acronym: designation, name, content: acronymRule(designation, label) });
|
|
4365
|
+
}
|
|
4366
|
+
for (const hidden of term.record["hidden-labels"] ?? []) {
|
|
4367
|
+
deprecated.add(bounded(escapeRegex(hidden), hidden), swapValue(label));
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
const files = [];
|
|
4371
|
+
const add = (name, content) => {
|
|
4372
|
+
files.push({ path: join2(termsDir, `${name}.yml`), content });
|
|
4373
|
+
};
|
|
4374
|
+
if (casing.size > 0) {
|
|
4375
|
+
add("Casing", substitution("Use '%s' instead of '%s'.", "error", ["vocab: false"], casing));
|
|
4376
|
+
}
|
|
4377
|
+
if (lowercase.size > 0) {
|
|
4378
|
+
add(
|
|
4379
|
+
"Lowercase",
|
|
4380
|
+
substitution("Write '%[2]s' in lowercase, except to start a sentence.", "error", ["vocab: false"], lowercase)
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
if (sentenceStart.size > 0) {
|
|
4384
|
+
add(
|
|
4385
|
+
"SentenceStart",
|
|
4386
|
+
substitution(
|
|
4387
|
+
"Write '%[2]s' as the term list spells it, with a capital only to start a sentence.",
|
|
4388
|
+
"error",
|
|
4389
|
+
["vocab: false"],
|
|
4390
|
+
sentenceStart
|
|
4391
|
+
)
|
|
4392
|
+
);
|
|
4393
|
+
}
|
|
4394
|
+
if (deprecated.size > 0) {
|
|
4395
|
+
add("Deprecated", substitution("Use '%s' instead of '%s'.", "warning", ["matchcase: true"], deprecated));
|
|
4396
|
+
}
|
|
4397
|
+
for (const acronym of acronyms) add(acronym.name, acronym.content);
|
|
4398
|
+
const produced = new Set(files.map((f) => relative2(termsDir, f.path).toLowerCase()));
|
|
4399
|
+
const removals = marked.filter((path) => !produced.has(relative2(termsDir, path).toLowerCase())).sort();
|
|
4400
|
+
return { files, removals, dropped: droppedFields(terms, HOLDS3), skipped: [] };
|
|
4401
|
+
}
|
|
4402
|
+
};
|
|
4403
|
+
|
|
4404
|
+
// src/term/core/writers/interchange.ts
|
|
4405
|
+
var INTERCHANGE_WRITERS = [tbxWriter, skosWriter, csvWriter, jsonWriter, valeWriter];
|
|
4406
|
+
|
|
4407
|
+
// src/term/core/writers/index.ts
|
|
4408
|
+
var TERM_WRITERS = [...DOCUMENT_WRITERS, ...INTERCHANGE_WRITERS];
|
|
4409
|
+
|
|
4410
|
+
// src/term/commands/formats.ts
|
|
4411
|
+
var FORMATS_FORMATS = ["pretty", "json"];
|
|
4412
|
+
var DOCUMENT_RENDERS = {
|
|
4413
|
+
markdown: { input: "markdown", shapes: { directory: "page", file: "markdown-deflist" } },
|
|
4414
|
+
mdx: { input: "mdx", shapes: { directory: "page", file: "markdown-deflist" } },
|
|
4415
|
+
asciidoc: { input: "asciidoc", shapes: { directory: "page", file: "asciidoc-glossary" } },
|
|
4416
|
+
rst: { input: "rst", shapes: { directory: "page", file: "rst-glossary" } },
|
|
4417
|
+
html: { input: "html", shapes: { directory: "page", file: "html-dl" } },
|
|
4418
|
+
dita: { input: "xml", shapes: { directory: "dita-glossentry", file: "dita-glossgroup" } },
|
|
4419
|
+
docbook: { input: "xml", shapes: { file: "docbook-glossary" } }
|
|
4420
|
+
};
|
|
4421
|
+
var INTERCHANGE_LABELS = {
|
|
4422
|
+
tbx: "TBX v2 Core",
|
|
4423
|
+
skos: "JSON-LD",
|
|
4424
|
+
vale: "style"
|
|
4425
|
+
};
|
|
4426
|
+
function rendered(writers, format, construct) {
|
|
4427
|
+
return writers.some((writer) => {
|
|
4428
|
+
const render3 = DOCUMENT_RENDERS[writer.format];
|
|
4429
|
+
if (render3?.input !== format) return false;
|
|
4430
|
+
return writer.shapes.some((shape) => render3.shapes[shape] === construct);
|
|
4431
|
+
});
|
|
4432
|
+
}
|
|
4433
|
+
function listFormats(opts = {}) {
|
|
4434
|
+
const readers = opts.readers ?? TERM_READERS;
|
|
4435
|
+
const writers = opts.writers ?? TERM_WRITERS;
|
|
4436
|
+
const formats = [];
|
|
4437
|
+
for (const reader of readers) {
|
|
4438
|
+
for (const format of reader.formats) if (!formats.includes(format)) formats.push(format);
|
|
4439
|
+
}
|
|
4440
|
+
const rows = [];
|
|
4441
|
+
for (const format of formats) {
|
|
4442
|
+
for (const reader of readers.filter((r) => r.formats.includes(format))) {
|
|
4443
|
+
rows.push({
|
|
4444
|
+
format,
|
|
4445
|
+
construct: reader.construct,
|
|
4446
|
+
label: reader.label,
|
|
4447
|
+
read: true,
|
|
4448
|
+
write: reader.apply !== void 0 || rendered(writers, format, reader.construct)
|
|
4449
|
+
});
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
for (const writer of writers) {
|
|
4453
|
+
if (DOCUMENT_RENDERS[writer.format] !== void 0) continue;
|
|
4454
|
+
rows.push({ format: writer.format, label: INTERCHANGE_LABELS[writer.format] ?? "", read: false, write: true });
|
|
4455
|
+
}
|
|
4456
|
+
return rows;
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
// src/term/commands/get.ts
|
|
4460
|
+
var GET_FORMATS = ["pretty", "json"];
|
|
4461
|
+
function findTerm(terms, query) {
|
|
4462
|
+
const folded = query.toLowerCase();
|
|
4463
|
+
return terms.find((t) => t.id === query) ?? terms.find((t) => t.record.label.toLowerCase() === folded) ?? terms.find((t) => (t.record["alt-labels"] ?? []).some((alt) => alt.toLowerCase() === folded));
|
|
4464
|
+
}
|
|
4465
|
+
function editDistance(a, b) {
|
|
4466
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
4467
|
+
for (let i = 1; i <= a.length; i++) {
|
|
4468
|
+
const row = [i];
|
|
4469
|
+
for (let j = 1; j <= b.length; j++) {
|
|
4470
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
4471
|
+
row.push(Math.min((row[j - 1] ?? 0) + 1, (prev[j] ?? 0) + 1, (prev[j - 1] ?? 0) + cost));
|
|
4472
|
+
}
|
|
4473
|
+
prev = row;
|
|
4474
|
+
}
|
|
4475
|
+
return prev[b.length] ?? 0;
|
|
4476
|
+
}
|
|
4477
|
+
function closestName(query, names) {
|
|
4478
|
+
const limit = Math.max(2, Math.floor(query.length / 3));
|
|
4479
|
+
let best;
|
|
4480
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
4481
|
+
for (const name of names) {
|
|
4482
|
+
const distance = editDistance(query, name);
|
|
4483
|
+
if (distance < bestDistance) {
|
|
4484
|
+
best = name;
|
|
4485
|
+
bestDistance = distance;
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
return bestDistance <= limit ? best : void 0;
|
|
4489
|
+
}
|
|
4490
|
+
async function runGet(opts) {
|
|
4491
|
+
const { set } = await loadTerms(opts);
|
|
4492
|
+
const found = findTerm(set.terms, opts.term);
|
|
4493
|
+
if (found !== void 0) return found;
|
|
4494
|
+
const nearest = closestName(
|
|
4495
|
+
opts.term,
|
|
4496
|
+
set.terms.flatMap((t) => [t.id, t.record.label])
|
|
4497
|
+
);
|
|
4498
|
+
const hint = nearest === void 0 ? "." : `; did you mean "${nearest}"?`;
|
|
4499
|
+
throw new TermError(`no term "${opts.term}". ${plural3(set.terms.length, "term")}${hint}`);
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
// src/term/core/lint.ts
|
|
4503
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4504
|
+
import { tmpdir } from "node:os";
|
|
4505
|
+
import { join as join3 } from "node:path";
|
|
4506
|
+
|
|
4507
|
+
// src/term/core/vale.ts
|
|
4508
|
+
import { execFile } from "node:child_process";
|
|
4509
|
+
var MAX_BUFFER = 64 * 1024 * 1024;
|
|
4510
|
+
var SEVERITIES2 = ["error", "warning", "suggestion"];
|
|
4511
|
+
var spawnVale = (args, opts) => new Promise((settle, reject) => {
|
|
4512
|
+
execFile(
|
|
4513
|
+
"vale",
|
|
4514
|
+
args,
|
|
4515
|
+
{ cwd: opts.cwd, windowsHide: true, maxBuffer: MAX_BUFFER, encoding: "utf8" },
|
|
4516
|
+
(error, stdout, stderr) => {
|
|
4517
|
+
if (!error) {
|
|
4518
|
+
settle({ code: 0, stdout, stderr });
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
if (typeof error.code === "number") {
|
|
4522
|
+
settle({ code: error.code, stdout, stderr });
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4525
|
+
if (error.code === void 0 && error.signal) {
|
|
4526
|
+
settle({ code: null, stdout, stderr });
|
|
4527
|
+
return;
|
|
4528
|
+
}
|
|
4529
|
+
const failure = error;
|
|
4530
|
+
reject(failure);
|
|
4531
|
+
}
|
|
4532
|
+
);
|
|
4533
|
+
});
|
|
4534
|
+
function isEnoent(err) {
|
|
4535
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
4536
|
+
}
|
|
4537
|
+
async function start(spawn, args, cwd, missing) {
|
|
4538
|
+
try {
|
|
4539
|
+
return await spawn(args, { cwd });
|
|
4540
|
+
} catch (err) {
|
|
4541
|
+
if (isEnoent(err)) throw new TermError(missing);
|
|
4542
|
+
throw err;
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
function isMapping2(value) {
|
|
4546
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4547
|
+
}
|
|
4548
|
+
function failureText(result) {
|
|
4549
|
+
const stderr = result.stderr.trim();
|
|
4550
|
+
try {
|
|
4551
|
+
const parsed = JSON.parse(stderr);
|
|
4552
|
+
if (isMapping2(parsed) && typeof parsed["Text"] === "string") return parsed["Text"].trim();
|
|
4553
|
+
} catch {
|
|
4554
|
+
}
|
|
4555
|
+
return stderr === "" ? `exit code ${String(result.code)}` : stderr;
|
|
4556
|
+
}
|
|
4557
|
+
function parseJson(stdout, what) {
|
|
4558
|
+
try {
|
|
4559
|
+
return JSON.parse(stdout);
|
|
4560
|
+
} catch {
|
|
4561
|
+
throw new TermError(`vale printed ${what} that is not JSON: ${stdout.trim().slice(0, 200)}`);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
function parseAlert(raw, path) {
|
|
4565
|
+
const unreadable = () => new TermError(`vale printed an alert for ${path} that manni cannot read: ${JSON.stringify(raw)}`);
|
|
4566
|
+
if (!isMapping2(raw)) throw unreadable();
|
|
4567
|
+
const { Check, Message, Line, Span, Severity } = raw;
|
|
4568
|
+
if (typeof Check !== "string" || typeof Message !== "string" || typeof Line !== "number") throw unreadable();
|
|
4569
|
+
if (!Array.isArray(Span) || Span.length !== 2) throw unreadable();
|
|
4570
|
+
const span = Span;
|
|
4571
|
+
const [from, to] = span;
|
|
4572
|
+
if (typeof from !== "number" || typeof to !== "number") throw unreadable();
|
|
4573
|
+
const severity = SEVERITIES2.find((s) => s === Severity);
|
|
4574
|
+
if (severity === void 0) throw unreadable();
|
|
4575
|
+
return { Check, Message, Line, Span: [from, to], Severity: severity };
|
|
4576
|
+
}
|
|
4577
|
+
function parseAlerts(stdout) {
|
|
4578
|
+
const parsed = parseJson(stdout, "alerts");
|
|
4579
|
+
if (!isMapping2(parsed)) throw new TermError("vale printed alerts that are not a JSON object.");
|
|
4580
|
+
const alerts = {};
|
|
4581
|
+
for (const [path, list] of Object.entries(parsed)) {
|
|
4582
|
+
if (!Array.isArray(list)) throw new TermError(`vale printed alerts for ${path} that are not a list.`);
|
|
4583
|
+
alerts[path] = list.map((raw) => parseAlert(raw, path));
|
|
4584
|
+
}
|
|
4585
|
+
return alerts;
|
|
4586
|
+
}
|
|
4587
|
+
async function runValeJson(files, opts, spawn = spawnVale) {
|
|
4588
|
+
const args = ["--output=JSON", ...opts.config === void 0 ? [] : ["--config", opts.config], ...files];
|
|
4589
|
+
const result = await start(spawn, args, opts.cwd, "vale is not on PATH. Install Vale to lint definitions.");
|
|
4590
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
4591
|
+
throw new TermError(`vale could not lint definitions: ${failureText(result)}`);
|
|
4592
|
+
}
|
|
4593
|
+
return parseAlerts(result.stdout);
|
|
4594
|
+
}
|
|
4595
|
+
var NO_CONFIG = /no config file found|\.vale\.ini not found/i;
|
|
4596
|
+
function stringList(value) {
|
|
4597
|
+
if (!Array.isArray(value)) return void 0;
|
|
4598
|
+
const list = [];
|
|
4599
|
+
for (const item2 of value) {
|
|
4600
|
+
if (typeof item2 !== "string") return void 0;
|
|
4601
|
+
list.push(item2);
|
|
4602
|
+
}
|
|
4603
|
+
return list;
|
|
4604
|
+
}
|
|
4605
|
+
async function valeLsConfig(opts, spawn = spawnVale) {
|
|
4606
|
+
const args = [...opts.config === void 0 ? [] : ["--config", opts.config], "ls-config"];
|
|
4607
|
+
const result = await start(
|
|
4608
|
+
spawn,
|
|
4609
|
+
args,
|
|
4610
|
+
opts.cwd,
|
|
4611
|
+
"vale is not on PATH. Install Vale, or pass -o <styles directory>."
|
|
4612
|
+
);
|
|
4613
|
+
if (result.code !== 0) {
|
|
4614
|
+
const text2 = failureText(result);
|
|
4615
|
+
if (NO_CONFIG.test(text2)) return null;
|
|
4616
|
+
throw new TermError(`vale could not resolve its configuration: ${text2}`);
|
|
4617
|
+
}
|
|
4618
|
+
const parsed = parseJson(result.stdout, "a configuration");
|
|
4619
|
+
if (!isMapping2(parsed)) throw new TermError("vale printed a configuration that is not a JSON object.");
|
|
4620
|
+
const paths = stringList(parsed["Paths"]);
|
|
4621
|
+
const stylesPath = paths?.[paths.length - 1];
|
|
4622
|
+
if (stylesPath === void 0 || stylesPath === "") {
|
|
4623
|
+
throw new TermError(
|
|
4624
|
+
"vale's configuration names no styles directory. Set StylesPath in it, or pass -o <styles directory>."
|
|
4625
|
+
);
|
|
4626
|
+
}
|
|
4627
|
+
const root = parsed["RootINI"];
|
|
4628
|
+
const configFiles = stringList(parsed["ConfigFiles"]);
|
|
4629
|
+
const rootIni = typeof root === "string" && root !== "" ? root : configFiles?.[configFiles.length - 1] ?? opts.config ?? "";
|
|
4630
|
+
const baseStyles = {};
|
|
4631
|
+
const sections = parsed["SBaseStyles"];
|
|
4632
|
+
if (sections !== null && sections !== void 0) {
|
|
4633
|
+
if (!isMapping2(sections)) throw new TermError("vale printed SBaseStyles that is not a JSON object.");
|
|
4634
|
+
for (const [glob, styles] of Object.entries(sections)) {
|
|
4635
|
+
const list = stringList(styles);
|
|
4636
|
+
if (list === void 0) throw new TermError(`vale printed styles for ${glob} that are not a list of names.`);
|
|
4637
|
+
baseStyles[glob] = list;
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
return { stylesPath, rootIni, baseStyles };
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
// src/term/core/lint.ts
|
|
4644
|
+
var PROSE_FIELDS = ["definition", "abstract", "scope-note"];
|
|
4645
|
+
function fileStem(id) {
|
|
4646
|
+
const safe = id.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
4647
|
+
return safe === "" ? "_" : safe;
|
|
4648
|
+
}
|
|
4649
|
+
function baseName(path) {
|
|
4650
|
+
const parts = path.split(/[\\/]/);
|
|
4651
|
+
return parts[parts.length - 1] ?? path;
|
|
4652
|
+
}
|
|
4653
|
+
function sourceLine(written, proseLine) {
|
|
4654
|
+
const { term, field, value } = written;
|
|
4655
|
+
const fieldLine = term.location.fieldLines[field] ?? term.location.line;
|
|
4656
|
+
if (!value.includes("\n")) return fieldLine;
|
|
4657
|
+
const { construct } = term.location;
|
|
4658
|
+
return construct === "page" || construct === "manifest" ? fieldLine + proseLine : fieldLine + proseLine - 1;
|
|
4659
|
+
}
|
|
4660
|
+
function compareFindings3(a, b) {
|
|
4661
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
4662
|
+
const lineA = a.line ?? 0;
|
|
4663
|
+
const lineB = b.line ?? 0;
|
|
4664
|
+
if (lineA !== lineB) return lineA - lineB;
|
|
4665
|
+
if (a.ruleId !== b.ruleId) return a.ruleId < b.ruleId ? -1 : 1;
|
|
4666
|
+
if (a.message !== b.message) return a.message < b.message ? -1 : 1;
|
|
4667
|
+
return 0;
|
|
4668
|
+
}
|
|
4669
|
+
async function lintTermSet(set, opts = {}) {
|
|
4670
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4671
|
+
const files = [];
|
|
4672
|
+
const stemCount = /* @__PURE__ */ new Map();
|
|
4673
|
+
for (const term of set.terms) {
|
|
4674
|
+
const fields = PROSE_FIELDS.filter((field) => term.record[field] !== void 0);
|
|
4675
|
+
if (fields.length === 0) continue;
|
|
4676
|
+
const stem = fileStem(term.id);
|
|
4677
|
+
const seen = (stemCount.get(stem.toLowerCase()) ?? 0) + 1;
|
|
4678
|
+
stemCount.set(stem.toLowerCase(), seen);
|
|
4679
|
+
const prefix = seen === 1 ? stem : `${stem}.${String(seen)}`;
|
|
4680
|
+
for (const field of fields) {
|
|
4681
|
+
const value = term.record[field];
|
|
4682
|
+
if (value === void 0) continue;
|
|
4683
|
+
const name = `${prefix}.${field}.md`;
|
|
4684
|
+
byName.set(name.toLowerCase(), { term, field, value });
|
|
4685
|
+
files.push({ name, content: `${value}
|
|
4686
|
+
` });
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (files.length === 0) return [];
|
|
4690
|
+
const runVale = opts.runVale ?? runValeJson;
|
|
4691
|
+
const dir = await mkdtemp(join3(tmpdir(), "manni-term-lint-"));
|
|
4692
|
+
try {
|
|
4693
|
+
for (const file of files) await writeFile(join3(dir, file.name), file.content, "utf8");
|
|
4694
|
+
const alerts = await runVale([dir], { config: opts.valeConfig, cwd: opts.cwd });
|
|
4695
|
+
const findings = [];
|
|
4696
|
+
for (const [path, list] of Object.entries(alerts)) {
|
|
4697
|
+
const written = byName.get(baseName(path).toLowerCase());
|
|
4698
|
+
if (written === void 0) continue;
|
|
4699
|
+
for (const alert of list) {
|
|
4700
|
+
findings.push({
|
|
4701
|
+
ruleId: proseRuleId(alert.Check),
|
|
4702
|
+
severity: foldValeSeverity(alert.Severity),
|
|
4703
|
+
message: alert.Message,
|
|
4704
|
+
file: written.term.location.file,
|
|
4705
|
+
line: sourceLine(written, alert.Line),
|
|
4706
|
+
id: written.term.id,
|
|
4707
|
+
tool: "vale",
|
|
4708
|
+
check: alert.Check,
|
|
4709
|
+
toolSeverity: alert.Severity,
|
|
4710
|
+
field: written.field
|
|
4711
|
+
});
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
return findings.sort(compareFindings3);
|
|
4715
|
+
} finally {
|
|
4716
|
+
await rm(dir, { recursive: true, force: true });
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
// src/term/commands/lint.ts
|
|
4721
|
+
async function runLint(opts) {
|
|
4722
|
+
const { cwd, run, set } = await loadTerms(opts);
|
|
4723
|
+
const valeConfig = valeConfigFor(run);
|
|
4724
|
+
const findings = await lintTermSet(set, {
|
|
4725
|
+
cwd,
|
|
4726
|
+
...valeConfig === void 0 ? {} : { valeConfig },
|
|
4727
|
+
...opts.runVale === void 0 ? {} : { runVale: opts.runVale }
|
|
4728
|
+
});
|
|
4729
|
+
const { results, origin } = toResults(set, findings);
|
|
4730
|
+
return {
|
|
4731
|
+
findings: reportedFindings(results, origin),
|
|
4732
|
+
results,
|
|
4733
|
+
summary: summarize2(results),
|
|
4734
|
+
frame: { cwd, base: run.configDir ?? cwd, runBase: run.base },
|
|
4735
|
+
terms: set.terms.length,
|
|
4736
|
+
references: set.references.length,
|
|
4737
|
+
found: findings.length,
|
|
4738
|
+
cwd
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4742
|
+
// src/term/commands/list.ts
|
|
4743
|
+
var LIST_FORMATS = ["pretty", "json", "csv"];
|
|
4744
|
+
async function runList(opts) {
|
|
4745
|
+
const { set } = await loadTerms(opts);
|
|
4746
|
+
return { terms: set.terms, set };
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
// src/term/commands/write.ts
|
|
4750
|
+
import { existsSync as existsSync3, statSync } from "node:fs";
|
|
4751
|
+
import { lstat, mkdir, readdir, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
4752
|
+
import { dirname, extname as extname2, join as join4, resolve as resolve5 } from "node:path";
|
|
4753
|
+
var SUGGESTED_SECTION = "*.md";
|
|
4754
|
+
var STYLE2 = "Terms";
|
|
4755
|
+
function writeFormats(writers = TERM_WRITERS) {
|
|
4756
|
+
return TERM_WRITE_FORMATS.filter((format) => writers.some((writer) => writer.format === format));
|
|
4757
|
+
}
|
|
4758
|
+
function isDirectory(path) {
|
|
4759
|
+
try {
|
|
4760
|
+
return statSync(path).isDirectory();
|
|
4761
|
+
} catch {
|
|
4762
|
+
return false;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
var MAX_EXISTING_FILE_BYTES = 1024 * 1024;
|
|
4766
|
+
async function filesUnder(dir, into) {
|
|
4767
|
+
let entries;
|
|
4768
|
+
try {
|
|
4769
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
4770
|
+
} catch {
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4773
|
+
for (const entry of entries) {
|
|
4774
|
+
const path = join4(dir, entry.name);
|
|
4775
|
+
if (entry.isDirectory()) await filesUnder(path, into);
|
|
4776
|
+
else if (entry.isFile()) {
|
|
4777
|
+
const { size } = await lstat(path);
|
|
4778
|
+
into.set(path, size > MAX_EXISTING_FILE_BYTES ? "" : await readFile2(path, "utf8"));
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
async function existingFor(target) {
|
|
4783
|
+
const existing = /* @__PURE__ */ new Map();
|
|
4784
|
+
if (target.shape === "directory") {
|
|
4785
|
+
await filesUnder(target.path, existing);
|
|
4786
|
+
} else if (existsSync3(target.path) && !isDirectory(target.path)) {
|
|
4787
|
+
existing.set(target.path, await readFile2(target.path, "utf8"));
|
|
4788
|
+
}
|
|
4789
|
+
return existing;
|
|
4790
|
+
}
|
|
4791
|
+
function relativized(error, paths, cwd) {
|
|
4792
|
+
let message = error.message;
|
|
4793
|
+
for (const path of [...paths].sort((a, b) => b.length - a.length)) {
|
|
4794
|
+
if (message.includes(path)) message = message.replaceAll(path, displayPath(path, cwd));
|
|
4795
|
+
}
|
|
4796
|
+
return message === error.message ? error : new TermError(message);
|
|
4797
|
+
}
|
|
4798
|
+
function droppedFrom(format, shape, readers) {
|
|
4799
|
+
const construct = DOCUMENT_RENDERS[format]?.shapes[shape];
|
|
4800
|
+
const reader = construct === void 0 ? void 0 : readerForConstruct(construct, readers);
|
|
4801
|
+
return reader === void 0 ? format : `a ${reader.label}`;
|
|
4802
|
+
}
|
|
4803
|
+
async function applyChanges(files, removals, existing, cwd, write) {
|
|
4804
|
+
const changes = [];
|
|
4805
|
+
for (const file of files) {
|
|
4806
|
+
const before = existing.get(file.path) ?? (existsSync3(file.path) ? await readFile2(file.path, "utf8") : void 0);
|
|
4807
|
+
if (before === file.content) continue;
|
|
4808
|
+
changes.push({ path: displayPath(file.path, cwd), action: before === void 0 ? "create" : "change" });
|
|
4809
|
+
if (write) {
|
|
4810
|
+
await mkdir(dirname(file.path), { recursive: true });
|
|
4811
|
+
await writeFile2(file.path, file.content, "utf8");
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
for (const path of removals) {
|
|
4815
|
+
if (!existsSync3(path)) continue;
|
|
4816
|
+
changes.push({ path: displayPath(path, cwd), action: "remove" });
|
|
4817
|
+
if (write) await rm2(path, { force: true });
|
|
4818
|
+
}
|
|
4819
|
+
return changes;
|
|
4820
|
+
}
|
|
4821
|
+
async function runWrite(opts) {
|
|
4822
|
+
const cwd = resolve5(opts.cwd ?? process.cwd());
|
|
4823
|
+
const writers = opts.writers ?? TERM_WRITERS;
|
|
4824
|
+
const readers = opts.readers ?? TERM_READERS;
|
|
4825
|
+
const check = opts.check === true;
|
|
4826
|
+
const dryRun = opts.dryRun === true;
|
|
4827
|
+
if (check && dryRun) throw new TermError("--check and --dry-run cannot be combined.");
|
|
4828
|
+
if (opts.format === void 0) {
|
|
4829
|
+
if (opts.out !== void 0) throw new TermError("-o needs -f <format>.");
|
|
4830
|
+
return writeInPlace({ ...opts, cwd, readers }, check, dryRun);
|
|
4831
|
+
}
|
|
4832
|
+
const format = formatChoice(opts.format, writeFormats(writers));
|
|
4833
|
+
const writer = writers.find((w) => w.format === format);
|
|
4834
|
+
if (writer === void 0) throw new TermError(`unknown format "${format}".`);
|
|
4835
|
+
if (opts.out === void 0 && format !== "vale") {
|
|
4836
|
+
throw new TermError(`-f ${format} needs -o <path>.`);
|
|
4837
|
+
}
|
|
4838
|
+
const { run, set } = await loadTerms({ ...opts, cwd, readers });
|
|
4839
|
+
let targetPath;
|
|
4840
|
+
let wiring;
|
|
4841
|
+
if (opts.out === void 0) {
|
|
4842
|
+
const lsConfig = opts.lsConfig ?? ((o) => valeLsConfig(o));
|
|
4843
|
+
const config = valeConfigFor(run);
|
|
4844
|
+
const resolved = await lsConfig({ cwd, ...config === void 0 ? {} : { config } });
|
|
4845
|
+
if (resolved === null) {
|
|
4846
|
+
throw new TermError(
|
|
4847
|
+
"Vale found no config file. Set tools.vale.config in manni.config.yaml, or pass -o <styles directory>."
|
|
4848
|
+
);
|
|
4849
|
+
}
|
|
4850
|
+
targetPath = resolve5(cwd, resolved.stylesPath);
|
|
4851
|
+
if (!Object.values(resolved.baseStyles).some((styles) => styles.includes(STYLE2))) {
|
|
4852
|
+
const first = Object.entries(resolved.baseStyles)[0];
|
|
4853
|
+
const [section, styles] = first ?? [SUGGESTED_SECTION, []];
|
|
4854
|
+
wiring = {
|
|
4855
|
+
rootIni: displayPath(resolve5(cwd, resolved.rootIni), cwd),
|
|
4856
|
+
section,
|
|
4857
|
+
styles: [...styles, STYLE2],
|
|
4858
|
+
addSection: first === void 0
|
|
4859
|
+
};
|
|
4860
|
+
}
|
|
4861
|
+
} else {
|
|
4862
|
+
targetPath = resolve5(cwd, opts.out);
|
|
4863
|
+
}
|
|
4864
|
+
const shape = format === "vale" || /[\\/]$/.test(opts.out ?? "") || isDirectory(targetPath) ? "directory" : "file";
|
|
4865
|
+
const target = { path: targetPath, shape };
|
|
4866
|
+
const existing = await existingFor(target);
|
|
4867
|
+
let render3;
|
|
4868
|
+
try {
|
|
4869
|
+
render3 = writer.render(set.terms, target, { existing });
|
|
4870
|
+
} catch (error) {
|
|
4871
|
+
if (error instanceof TermError) throw relativized(error, [...existing.keys(), targetPath], cwd);
|
|
4872
|
+
throw error;
|
|
4873
|
+
}
|
|
4874
|
+
const changes = await applyChanges(render3.files, render3.removals, existing, cwd, !check && !dryRun);
|
|
4875
|
+
const shown = format === "vale" ? displayPath(join4(targetPath, STYLE2), cwd) : opts.out ?? displayPath(targetPath, cwd);
|
|
4876
|
+
return {
|
|
4877
|
+
mode: "render",
|
|
4878
|
+
format,
|
|
4879
|
+
shape,
|
|
4880
|
+
target: shown,
|
|
4881
|
+
terms: set.terms.length,
|
|
4882
|
+
changes,
|
|
4883
|
+
files: render3.files.map((file) => ({ path: displayPath(file.path, cwd), content: file.content })),
|
|
4884
|
+
// A Vale style holds designations only, by design; its dropped definitions are not news.
|
|
4885
|
+
dropped: format === "vale" ? [] : render3.dropped,
|
|
4886
|
+
droppedFrom: droppedFrom(format, shape, readers),
|
|
4887
|
+
skipped: render3.skipped,
|
|
4888
|
+
check,
|
|
4889
|
+
dryRun,
|
|
4890
|
+
...wiring === void 0 ? {} : { wiring }
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4893
|
+
function inputFor(content, path, construct, file) {
|
|
4894
|
+
if (construct === "manifest") {
|
|
4895
|
+
return { content, file, path, format: MANIFEST_FORMAT, metadata: {}, lineFor: () => void 0 };
|
|
4896
|
+
}
|
|
4897
|
+
const extractor = extractorForExtension(extname2(path));
|
|
4898
|
+
if (extractor === void 0) throw new TermError(`${file}: no reader for its extension.`);
|
|
4899
|
+
return extractInput(content, file, path, extractor.name);
|
|
4900
|
+
}
|
|
4901
|
+
function carryRecords(current, edited) {
|
|
4902
|
+
const count = (terms, id) => terms.filter((t) => t.id === id).length;
|
|
4903
|
+
return current.map((entry, index) => {
|
|
4904
|
+
const unique2 = count(current, entry.id) === 1 && count(edited, entry.id) === 1;
|
|
4905
|
+
const match = unique2 ? edited.find((t) => t.id === entry.id) : edited[index];
|
|
4906
|
+
return match?.id === entry.id ? { ...entry, record: match.record } : entry;
|
|
4907
|
+
});
|
|
4908
|
+
}
|
|
4909
|
+
function applyInPlace(original, path, file, constructs, readers) {
|
|
4910
|
+
let content = original;
|
|
4911
|
+
let first = true;
|
|
4912
|
+
for (const [construct, terms] of constructs) {
|
|
4913
|
+
const reader = readerForConstruct(construct, readers);
|
|
4914
|
+
if (reader?.apply === void 0) continue;
|
|
4915
|
+
const input = inputFor(content, path, construct, file);
|
|
4916
|
+
content = reader.apply(input, first ? terms : carryRecords(reader.read(input).terms, terms));
|
|
4917
|
+
first = false;
|
|
4918
|
+
}
|
|
4919
|
+
return content;
|
|
4920
|
+
}
|
|
4921
|
+
async function writeInPlace(opts, check, dryRun) {
|
|
4922
|
+
const { cwd, readers } = opts;
|
|
4923
|
+
const { set } = await loadTerms(opts);
|
|
4924
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
4925
|
+
for (const term of set.terms) {
|
|
4926
|
+
const { path, construct } = term.location;
|
|
4927
|
+
if (path === void 0) {
|
|
4928
|
+
throw new TermError(`${term.location.file} has nowhere to write back to. Pass -f <format> -o <path>.`);
|
|
4929
|
+
}
|
|
4930
|
+
const reader = readerForConstruct(construct, readers);
|
|
4931
|
+
if (reader?.apply === void 0) {
|
|
4932
|
+
const name = reader?.label ?? construct;
|
|
4933
|
+
throw new TermError(
|
|
4934
|
+
`${displayPath(term.location.file, cwd)}: a ${name} cannot be written in place. Pass -f <format> -o <path>.`
|
|
4935
|
+
);
|
|
4936
|
+
}
|
|
4937
|
+
const entry = byPath.get(path) ?? { file: term.location.file, constructs: /* @__PURE__ */ new Map() };
|
|
4938
|
+
entry.constructs.set(construct, [...entry.constructs.get(construct) ?? [], term]);
|
|
4939
|
+
byPath.set(path, entry);
|
|
4940
|
+
}
|
|
4941
|
+
const files = [];
|
|
4942
|
+
const existing = /* @__PURE__ */ new Map();
|
|
4943
|
+
for (const [path, { file, constructs }] of byPath) {
|
|
4944
|
+
let original;
|
|
4945
|
+
try {
|
|
4946
|
+
original = await readFile2(path, "utf8");
|
|
4947
|
+
} catch (error) {
|
|
4948
|
+
throw new TermError(`${file}: could not be read: ${errorMessage(error)}`);
|
|
4949
|
+
}
|
|
4950
|
+
existing.set(path, original);
|
|
4951
|
+
files.push({ path, content: applyInPlace(original, path, file, constructs, readers) });
|
|
4952
|
+
}
|
|
4953
|
+
const changes = await applyChanges(files, [], existing, cwd, !check && !dryRun);
|
|
4954
|
+
return {
|
|
4955
|
+
mode: "in-place",
|
|
4956
|
+
terms: set.terms.length,
|
|
4957
|
+
changes,
|
|
4958
|
+
files: files.map((f) => ({ path: displayPath(f.path, cwd), content: f.content })),
|
|
4959
|
+
dropped: [],
|
|
4960
|
+
skipped: [],
|
|
4961
|
+
check,
|
|
4962
|
+
dryRun
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
|
|
4966
|
+
// src/term/reporters/csv.ts
|
|
4967
|
+
var LIST_SEPARATOR2 = "|";
|
|
4968
|
+
function cell2(value) {
|
|
4969
|
+
return /[",\r\n]/.test(value) ? `"${value.replaceAll('"', '""')}"` : value;
|
|
4970
|
+
}
|
|
4971
|
+
function renderListCsv(terms) {
|
|
4972
|
+
const rows = [
|
|
4973
|
+
["id", "label", "alt-labels", "abstract"],
|
|
4974
|
+
...terms.map((t) => [
|
|
4975
|
+
t.id,
|
|
4976
|
+
t.record.label,
|
|
4977
|
+
(t.record["alt-labels"] ?? []).join(LIST_SEPARATOR2),
|
|
4978
|
+
t.record.abstract ?? ""
|
|
4979
|
+
])
|
|
4980
|
+
];
|
|
4981
|
+
return rows.map((row) => row.map(cell2).join(",")).join("\n");
|
|
4982
|
+
}
|
|
4983
|
+
|
|
4984
|
+
// src/term/reporters/github.ts
|
|
4985
|
+
function renderFindingsGithub(report) {
|
|
4986
|
+
return report.findings.map((finding) => {
|
|
4987
|
+
const params = [`file=${escapeWorkflowCommandProperty(displayPath(finding.file, report.cwd))}`];
|
|
4988
|
+
if (finding.line !== void 0) params.push(`line=${String(finding.line)}`);
|
|
4989
|
+
params.push(`title=${escapeWorkflowCommandProperty(finding.ruleId)}`);
|
|
4990
|
+
return `::${finding.severity} ${params.join(",")}::${escapeWorkflowCommandMessage(finding.message)}`;
|
|
4991
|
+
}).join("\n");
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
// src/term/reporters/json.ts
|
|
4995
|
+
function termEntry2(term) {
|
|
4996
|
+
const entry = { id: term.id };
|
|
4997
|
+
if (term.language !== void 0) entry["language"] = term.language;
|
|
4998
|
+
for (const field of TERM_FIELDS) {
|
|
4999
|
+
const value = term.record[field];
|
|
5000
|
+
if (value !== void 0) entry[field] = value;
|
|
5001
|
+
}
|
|
5002
|
+
return entry;
|
|
5003
|
+
}
|
|
5004
|
+
function renderListJson(terms) {
|
|
5005
|
+
return JSON.stringify({ terms: terms.map(termEntry2) }, null, 2);
|
|
5006
|
+
}
|
|
5007
|
+
function renderGetJson(term) {
|
|
5008
|
+
return JSON.stringify(termEntry2(term), null, 2);
|
|
5009
|
+
}
|
|
5010
|
+
function renderFindingsJson(report) {
|
|
5011
|
+
const count = (severity) => report.findings.filter((f) => f.severity === severity).length;
|
|
5012
|
+
return JSON.stringify(
|
|
5013
|
+
{
|
|
5014
|
+
findings: report.findings.map((finding) => ({ ...finding, file: displayPath(finding.file, report.cwd) })),
|
|
5015
|
+
summary: {
|
|
5016
|
+
terms: report.terms,
|
|
5017
|
+
references: report.references,
|
|
5018
|
+
errors: count("error"),
|
|
5019
|
+
warnings: count("warning"),
|
|
5020
|
+
notices: count("notice"),
|
|
5021
|
+
...report.baseline === void 0 ? {} : { baseline: report.baseline }
|
|
5022
|
+
}
|
|
5023
|
+
},
|
|
5024
|
+
null,
|
|
5025
|
+
2
|
|
5026
|
+
);
|
|
5027
|
+
}
|
|
5028
|
+
function renderFormatsJson(rows) {
|
|
5029
|
+
return JSON.stringify({ formats: rows }, null, 2);
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
// src/term/reporters/sarif.ts
|
|
5033
|
+
var INFORMATION_URI = "https://hawkeyexl.github.io/manni/term/";
|
|
5034
|
+
var RULES_URI = "https://hawkeyexl.github.io/manni/term/reference/rules/";
|
|
5035
|
+
var RULE_DESCRIPTIONS = {
|
|
5036
|
+
"undefined-term": "A page's concepts: names a label no entry claims as its preferred label.",
|
|
5037
|
+
"duplicate-id": "Two entries share an id.",
|
|
5038
|
+
"label-collision": "Two entries claim the same preferred label, ignoring case.",
|
|
5039
|
+
"alt-label-collision": "An entry's alt-label is another entry's preferred label, ignoring case.",
|
|
5040
|
+
"dangling-reference": "A value of broader, narrower, related-terms or see names no entry, by label or by id.",
|
|
5041
|
+
"broader-cycle": "A chain of broader returns to where it started.",
|
|
5042
|
+
"see-not-empty": "An entry with see also carries a definition.",
|
|
5043
|
+
"asymmetric-hierarchy": "A names B as broader, and B omits A from narrower, or the reverse.",
|
|
5044
|
+
"abstract-too-long": "An abstract is longer than term.abstractMaxLength.",
|
|
5045
|
+
"unused-term": "No page's concepts: names the entry's preferred label."
|
|
5046
|
+
};
|
|
5047
|
+
var PROSE_PREFIX = `${RULE_ID_PREFIX}/prose/`;
|
|
5048
|
+
var FIELD_LABEL = new RegExp(`^(?:\\(root\\)|/(?:${TERM_FIELDS.join("|")})) `);
|
|
5049
|
+
function describe(rule) {
|
|
5050
|
+
if (rule.id.startsWith(PROSE_PREFIX)) {
|
|
5051
|
+
const check = rule.id.slice(PROSE_PREFIX.length);
|
|
5052
|
+
return { id: rule.id, shortDescription: { text: `Vale's ${check} rule flagged the prose of an entry's field.` } };
|
|
5053
|
+
}
|
|
5054
|
+
const name = rule.id.slice(RULE_ID_PREFIX.length + 1);
|
|
5055
|
+
if (!isTermRule2(name)) return rule;
|
|
5056
|
+
return { id: rule.id, shortDescription: { text: RULE_DESCRIPTIONS[name] }, helpUri: `${RULES_URI}#${name}` };
|
|
5057
|
+
}
|
|
5058
|
+
function renderFindingsSarif(report, opts = {}) {
|
|
5059
|
+
const log = JSON.parse(
|
|
5060
|
+
renderSarif(report.results, {
|
|
5061
|
+
frame: report.frame,
|
|
5062
|
+
...opts.onNotice === void 0 ? {} : { onNotice: opts.onNotice }
|
|
5063
|
+
})
|
|
5064
|
+
);
|
|
5065
|
+
for (const run of log.runs) {
|
|
5066
|
+
run.tool.driver.informationUri = INFORMATION_URI;
|
|
5067
|
+
run.tool.driver.rules = run.tool.driver.rules.map(describe);
|
|
5068
|
+
for (const result of run.results) result.message.text = result.message.text.replace(FIELD_LABEL, "");
|
|
5069
|
+
}
|
|
5070
|
+
return JSON.stringify(log, null, 2);
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5073
|
+
// src/term/reporters/junit.ts
|
|
5074
|
+
var MESSAGE_LABEL = new RegExp(`message="${FIELD_LABEL.source.slice(1)}`, "g");
|
|
5075
|
+
function renderFindingsJunit(report) {
|
|
5076
|
+
return renderJunit(report.results, { frame: report.frame, classname: JUNIT_CLASSNAME2 }).replace(
|
|
5077
|
+
MESSAGE_LABEL,
|
|
5078
|
+
'message="'
|
|
5079
|
+
);
|
|
5080
|
+
}
|
|
5081
|
+
|
|
5082
|
+
// src/term/reporters/pretty.ts
|
|
5083
|
+
var WIDTH = 80;
|
|
5084
|
+
function wrapWords(text2, width) {
|
|
5085
|
+
const lines = [];
|
|
5086
|
+
let line = "";
|
|
5087
|
+
for (const word of text2.split(/\s+/).filter((w) => w !== "")) {
|
|
5088
|
+
if (line === "") line = word;
|
|
5089
|
+
else if (line.length + 1 + word.length <= width) line = `${line} ${word}`;
|
|
5090
|
+
else {
|
|
5091
|
+
lines.push(line);
|
|
5092
|
+
line = word;
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
if (line !== "") lines.push(line);
|
|
5096
|
+
return lines.length === 0 ? [""] : lines;
|
|
5097
|
+
}
|
|
5098
|
+
function wrapSentences(text2, width) {
|
|
5099
|
+
if (text2.length <= width) return [text2];
|
|
5100
|
+
const lines = [];
|
|
5101
|
+
let line = "";
|
|
5102
|
+
for (const sentence of text2.split(/(?<=\.)\s+(?=\S)/)) {
|
|
5103
|
+
if (line === "") line = sentence;
|
|
5104
|
+
else if (line.length + 1 + sentence.length <= width) line = `${line} ${sentence}`;
|
|
5105
|
+
else {
|
|
5106
|
+
lines.push(line);
|
|
5107
|
+
line = sentence;
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
if (line !== "") lines.push(line);
|
|
5111
|
+
return lines;
|
|
5112
|
+
}
|
|
5113
|
+
function hanging(first, lines) {
|
|
5114
|
+
const indent = " ".repeat(first.length);
|
|
5115
|
+
return lines.map((line, i) => `${i === 0 ? first : indent}${line}`.trimEnd());
|
|
5116
|
+
}
|
|
5117
|
+
function renderListPretty(terms) {
|
|
5118
|
+
const idWidth = Math.max(0, ...terms.map((t) => t.id.length)) + 3;
|
|
5119
|
+
const labelWidth = Math.max(0, ...terms.map((t) => t.record.label.length)) + 3;
|
|
5120
|
+
const rows = terms.map(
|
|
5121
|
+
(t) => `${t.id.padEnd(idWidth)}${t.record.label.padEnd(labelWidth)}${(t.record["alt-labels"] ?? []).join(", ")}`.trimEnd()
|
|
5122
|
+
);
|
|
5123
|
+
return [...rows, plural3(terms.length, "term")].join("\n");
|
|
5124
|
+
}
|
|
5125
|
+
var GET_FIELDS = [
|
|
5126
|
+
"alt-labels",
|
|
5127
|
+
"hidden-labels",
|
|
5128
|
+
"broader",
|
|
5129
|
+
"narrower",
|
|
5130
|
+
"related-terms",
|
|
5131
|
+
"see",
|
|
5132
|
+
"abstract",
|
|
5133
|
+
"scope-note",
|
|
5134
|
+
"definition"
|
|
5135
|
+
];
|
|
5136
|
+
var KEY_WIDTH = Math.max("language".length, ...TERM_FIELDS.map((f) => f.length)) + 2;
|
|
5137
|
+
function renderGetPretty(term, opts) {
|
|
5138
|
+
const c = palette(opts.color);
|
|
5139
|
+
const where2 = `${displayPath(term.location.file, opts.cwd ?? process.cwd())}:${String(term.location.line)}`;
|
|
5140
|
+
const lines = [`${c.bold(term.record.label.padEnd(40))}${c.dim(where2)}`];
|
|
5141
|
+
const row = (key, value) => {
|
|
5142
|
+
const prefix = ` ${key.padEnd(KEY_WIDTH)}`;
|
|
5143
|
+
lines.push(...hanging(prefix, wrapWords(value, WIDTH - prefix.length)));
|
|
5144
|
+
};
|
|
5145
|
+
row("id", term.id);
|
|
5146
|
+
if (term.language !== void 0) row("language", term.language);
|
|
5147
|
+
for (const field of GET_FIELDS) {
|
|
5148
|
+
const value = term.record[field];
|
|
5149
|
+
if (value === void 0) continue;
|
|
5150
|
+
row(field, typeof value === "string" ? value : value.join(", "));
|
|
5151
|
+
}
|
|
5152
|
+
return lines.join("\n");
|
|
5153
|
+
}
|
|
5154
|
+
var RULE_WIDTH = 32;
|
|
5155
|
+
function severityText(c, severity) {
|
|
5156
|
+
const padded = severity.padEnd(6);
|
|
5157
|
+
if (severity === "error") return c.red(padded);
|
|
5158
|
+
if (severity === "warning") return c.yellow(padded);
|
|
5159
|
+
return c.cyan(padded);
|
|
5160
|
+
}
|
|
5161
|
+
function counts(findings) {
|
|
5162
|
+
const parts = [];
|
|
5163
|
+
const n = (severity) => findings.filter((f) => f.severity === severity).length;
|
|
5164
|
+
if (n("error") > 0) parts.push(plural3(n("error"), "error"));
|
|
5165
|
+
if (n("warning") > 0) parts.push(plural3(n("warning"), "warning"));
|
|
5166
|
+
if (n("notice") > 0) parts.push(plural3(n("notice"), "notice"));
|
|
5167
|
+
return parts.join(", ");
|
|
5168
|
+
}
|
|
5169
|
+
function renderFindingsPretty(report, opts) {
|
|
5170
|
+
const c = palette(opts.color);
|
|
5171
|
+
const { baseline } = report;
|
|
5172
|
+
if (baseline?.written === true) {
|
|
5173
|
+
return `${c.green("\u2713")} ${plural3(report.found, "finding")} recorded in ${baseline.path}`;
|
|
5174
|
+
}
|
|
5175
|
+
const baselined = baseline !== void 0 && baseline.suppressed > 0 ? `, ${String(baseline.suppressed)} baselined` : "";
|
|
5176
|
+
const terms = plural3(report.terms, "term");
|
|
5177
|
+
if (report.findings.length === 0) {
|
|
5178
|
+
const references2 = opts.references === false ? "" : `, ${plural3(report.references, "reference")}`;
|
|
5179
|
+
return `${c.green("\u2713")} ${terms}${references2}, no findings${baselined}`;
|
|
5180
|
+
}
|
|
5181
|
+
const ruleWidth = Math.max(RULE_WIDTH, ...report.findings.map((f) => f.ruleId.length + 2));
|
|
5182
|
+
const lines = [];
|
|
5183
|
+
let group;
|
|
5184
|
+
for (const finding of report.findings) {
|
|
5185
|
+
const file = displayPath(finding.file, report.cwd);
|
|
5186
|
+
const header = finding.line === void 0 ? file : `${file}:${String(finding.line)}`;
|
|
5187
|
+
if (header !== group) {
|
|
5188
|
+
lines.push(c.bold(header));
|
|
5189
|
+
group = header;
|
|
5190
|
+
}
|
|
5191
|
+
const prefixWidth = 2 + 6 + 1 + ruleWidth;
|
|
5192
|
+
const message = wrapSentences(finding.message, WIDTH - prefixWidth);
|
|
5193
|
+
const indent = " ".repeat(prefixWidth);
|
|
5194
|
+
message.forEach((text2, i) => {
|
|
5195
|
+
lines.push(
|
|
5196
|
+
i === 0 ? ` ${severityText(c, finding.severity)} ${finding.ruleId.padEnd(ruleWidth)}${text2}` : `${indent}${text2}`
|
|
5197
|
+
);
|
|
5198
|
+
});
|
|
5199
|
+
}
|
|
5200
|
+
lines.push("", `${counts(report.findings)} in ${terms}${baselined}`);
|
|
5201
|
+
return lines.join("\n");
|
|
5202
|
+
}
|
|
5203
|
+
function changeLine(change) {
|
|
5204
|
+
switch (change.action) {
|
|
5205
|
+
case "create":
|
|
5206
|
+
return `${change.path} would be created`;
|
|
5207
|
+
case "change":
|
|
5208
|
+
return `${change.path} would change`;
|
|
5209
|
+
case "remove":
|
|
5210
|
+
return `${change.path} would be removed`;
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
function swapCount(content) {
|
|
5214
|
+
const lines = content.split(/\r?\n/);
|
|
5215
|
+
const start2 = lines.indexOf("swap:");
|
|
5216
|
+
if (start2 === -1) return 0;
|
|
5217
|
+
return lines.slice(start2 + 1).filter((line) => line.startsWith(" ")).length;
|
|
5218
|
+
}
|
|
5219
|
+
function baseName2(path) {
|
|
5220
|
+
return path.split(/[\\/]/).pop() ?? path;
|
|
5221
|
+
}
|
|
5222
|
+
var CASING_RULES = /* @__PURE__ */ new Set(["Casing.yml", "Lowercase.yml", "SentenceStart.yml"]);
|
|
5223
|
+
function valeLines(report) {
|
|
5224
|
+
const rows = [];
|
|
5225
|
+
const acronyms = [];
|
|
5226
|
+
for (const file of report.files) {
|
|
5227
|
+
const name = baseName2(file.path);
|
|
5228
|
+
if (CASING_RULES.has(name)) rows.push([name, plural3(swapCount(file.content), "label")]);
|
|
5229
|
+
else if (name === "Deprecated.yml") rows.push([name, plural3(swapCount(file.content), "swap")]);
|
|
5230
|
+
else acronyms.push(name);
|
|
5231
|
+
}
|
|
5232
|
+
if (acronyms.length > 0) rows.push([acronyms.join(", "), plural3(acronyms.length, "acronym")]);
|
|
5233
|
+
const width = Math.max(0, ...rows.map(([name]) => name.length)) + 2;
|
|
5234
|
+
return rows.map(([name, what]) => ` ${name.padEnd(width)}${what}`);
|
|
5235
|
+
}
|
|
5236
|
+
function droppedLine(report) {
|
|
5237
|
+
if (report.dropped.length === 0) return void 0;
|
|
5238
|
+
const ids = /* @__PURE__ */ new Map();
|
|
5239
|
+
for (const { id, field } of report.dropped) {
|
|
5240
|
+
const set = ids.get(field) ?? /* @__PURE__ */ new Set();
|
|
5241
|
+
set.add(id);
|
|
5242
|
+
ids.set(field, set);
|
|
5243
|
+
}
|
|
5244
|
+
const fields = [...ids.entries()].map(([field, set]) => ({ field, n: set.size })).sort((a, b) => a.n - b.n || TERM_FIELDS.indexOf(a.field) - TERM_FIELDS.indexOf(b.field));
|
|
5245
|
+
const parts = fields.map(({ field, n }, i) => `${field} on ${i === 0 ? plural3(n, "term") : String(n)}`);
|
|
5246
|
+
return ` dropped from ${report.droppedFrom ?? report.format ?? "the render"}: ${parts.join(", ")}`;
|
|
5247
|
+
}
|
|
5248
|
+
function skippedLine(report) {
|
|
5249
|
+
if (report.skipped.length === 0) return void 0;
|
|
5250
|
+
const construct = report.droppedFrom ?? report.format ?? "the render";
|
|
5251
|
+
const count = plural3(report.skipped.length, "term");
|
|
5252
|
+
return ` skipped ${count} ${construct} cannot read without a definition: ${report.skipped.join(", ")}`;
|
|
5253
|
+
}
|
|
5254
|
+
function renderNotes(report) {
|
|
5255
|
+
return [droppedLine(report), skippedLine(report)].filter((line) => line !== void 0);
|
|
5256
|
+
}
|
|
5257
|
+
function unchangedLine(report) {
|
|
5258
|
+
return report.mode === "in-place" ? "Nothing to write" : `${report.target ?? ""} is up to date`;
|
|
5259
|
+
}
|
|
5260
|
+
function renderWritePretty(report) {
|
|
5261
|
+
if (report.check) {
|
|
5262
|
+
return report.changes.length > 0 ? report.changes.map(changeLine).join("\n") : unchangedLine(report);
|
|
5263
|
+
}
|
|
5264
|
+
if (report.dryRun) {
|
|
5265
|
+
const changes = report.changes.length > 0 ? report.changes.map(changeLine) : [unchangedLine(report)];
|
|
5266
|
+
return [...changes, ...renderNotes(report)].join("\n");
|
|
5267
|
+
}
|
|
5268
|
+
if (report.mode === "in-place") {
|
|
5269
|
+
return report.changes.length === 0 ? "Nothing to write" : `Wrote ${plural3(report.changes.length, "file")}`;
|
|
5270
|
+
}
|
|
5271
|
+
const each = report.shape === "directory" && report.format !== "vale" ? ", one file each" : "";
|
|
5272
|
+
const written = report.terms - report.skipped.length;
|
|
5273
|
+
const lines = [`Wrote ${plural3(written, "term")} to ${report.target ?? ""}${each}`];
|
|
5274
|
+
if (report.format === "vale") lines.push(...valeLines(report));
|
|
5275
|
+
lines.push(...renderNotes(report));
|
|
5276
|
+
return lines.join("\n");
|
|
5277
|
+
}
|
|
5278
|
+
function renderValeWiring(wiring) {
|
|
5279
|
+
return [
|
|
5280
|
+
wiring.addSection ? `notice: ${wiring.rootIni} has no section with BasedOnStyles. Add one that uses the Terms style:` : `notice: no section of ${wiring.rootIni} uses the Terms style. Add it to BasedOnStyles:`,
|
|
5281
|
+
` [${wiring.section}]`,
|
|
5282
|
+
` BasedOnStyles = ${wiring.styles.join(", ")}`
|
|
5283
|
+
].join("\n");
|
|
5284
|
+
}
|
|
5285
|
+
function renderFormatsPretty(rows) {
|
|
5286
|
+
const formatWidth = Math.max(0, ...rows.map((r) => r.format.length)) + 3;
|
|
5287
|
+
const labelWidth = Math.max(0, ...rows.map((r) => r.label.length)) + 3;
|
|
5288
|
+
return rows.map(
|
|
5289
|
+
(r) => `${r.format.padEnd(formatWidth)}${r.label.padEnd(labelWidth)}${r.read ? "read " : " "}${r.write ? "write" : ""}`.trimEnd()
|
|
5290
|
+
).join("\n");
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5293
|
+
// src/term/cli.ts
|
|
5294
|
+
function colorFor2(options) {
|
|
5295
|
+
return shouldColor({ noColor: !options.color, isTTY: process.stdout.isTTY });
|
|
5296
|
+
}
|
|
5297
|
+
async function coreOptions(paths, options) {
|
|
5298
|
+
return {
|
|
5299
|
+
inputs: paths,
|
|
5300
|
+
...paths.includes(STDIN_TOKEN) ? { stdin: await readStdin() } : {},
|
|
5301
|
+
...options.as === void 0 ? {} : { as: options.as },
|
|
5302
|
+
...options.ext === void 0 ? {} : { exts: splitList(options.ext) },
|
|
5303
|
+
exclude: options.exclude,
|
|
5304
|
+
collection: options.collection,
|
|
5305
|
+
...options.allowEmpty === true ? { allowEmpty: true } : {},
|
|
5306
|
+
...options.gitignore ? {} : { noGitignore: true },
|
|
5307
|
+
...options.config === void 0 ? {} : { configPath: options.config },
|
|
5308
|
+
onNotice: notice
|
|
5309
|
+
};
|
|
5310
|
+
}
|
|
5311
|
+
function withInputs(command) {
|
|
5312
|
+
return command.option("--as <format>", "input format for stdin (-)").option("--ext <list>", "comma-separated extensions for directory walks").option("--exclude <glob>", "glob to exclude; repeatable", collect, []).option("--collection <name>", "configured collection to read; repeatable", collect, []).option("--allow-empty", "treat zero matched files as success").option("--no-gitignore", "read files .gitignore covers").option("-c, --config <path>", "path to a manni config file").option("--no-color", "disable colored output");
|
|
5313
|
+
}
|
|
5314
|
+
function print(text2) {
|
|
5315
|
+
process.stdout.write(`${text2}
|
|
5316
|
+
`);
|
|
5317
|
+
}
|
|
5318
|
+
function renderFindings(report, format, options, references2) {
|
|
5319
|
+
let text2;
|
|
5320
|
+
switch (format) {
|
|
5321
|
+
case "pretty":
|
|
5322
|
+
text2 = renderFindingsPretty(report, { color: colorFor2(options), references: references2 });
|
|
5323
|
+
break;
|
|
5324
|
+
case "json":
|
|
5325
|
+
text2 = renderFindingsJson(report);
|
|
5326
|
+
break;
|
|
5327
|
+
case "github":
|
|
5328
|
+
text2 = renderFindingsGithub(report);
|
|
5329
|
+
break;
|
|
5330
|
+
case "sarif":
|
|
5331
|
+
text2 = renderFindingsSarif(report, { onNotice: notice });
|
|
5332
|
+
break;
|
|
5333
|
+
case "junit":
|
|
5334
|
+
text2 = renderFindingsJunit(report);
|
|
5335
|
+
break;
|
|
5336
|
+
}
|
|
5337
|
+
if (text2.length > 0 || !OMITTED_WHEN_CLEAN.has(format)) print(text2);
|
|
5338
|
+
process.exitCode = report.summary.failed > 0 ? 1 : 0;
|
|
5339
|
+
}
|
|
5340
|
+
function buildProgram5() {
|
|
5341
|
+
const program = new Command4();
|
|
5342
|
+
program.name("term").description("Check, lint and render a docset's terms and the references into them.").version(package_default.version, "-V, --version").showHelpAfterError("(add --help for usage)").exitOverride();
|
|
5343
|
+
withInputs(
|
|
5344
|
+
program.command("list").description("List the resolved entries").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
|
|
5345
|
+
).option("-f, --format <format>", `output: ${LIST_FORMATS.join(" | ")}`, "pretty").addHelpText(
|
|
5346
|
+
"after",
|
|
5347
|
+
[
|
|
5348
|
+
"",
|
|
5349
|
+
"Examples:",
|
|
5350
|
+
" manni term list # every collection",
|
|
5351
|
+
" manni term list docs/terms/ # one directory",
|
|
5352
|
+
" manni term list -f csv | head -2 # for a script"
|
|
5353
|
+
].join("\n")
|
|
5354
|
+
).action(async (paths, options) => {
|
|
5355
|
+
try {
|
|
5356
|
+
const format = formatChoice(options.format, LIST_FORMATS);
|
|
5357
|
+
const { terms } = await runList(await coreOptions(paths, options));
|
|
5358
|
+
print(format === "json" ? renderListJson(terms) : format === "csv" ? renderListCsv(terms) : renderListPretty(terms));
|
|
5359
|
+
process.exitCode = 0;
|
|
5360
|
+
} catch (err) {
|
|
5361
|
+
fail(err);
|
|
5362
|
+
}
|
|
5363
|
+
});
|
|
5364
|
+
withInputs(
|
|
5365
|
+
program.command("get").description("Show one entry").argument("<term>", "the entry's id, or its label or an alt-label in any case").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
|
|
5366
|
+
).option("-f, --format <format>", `output: ${GET_FORMATS.join(" | ")}`, "pretty").addHelpText(
|
|
5367
|
+
"after",
|
|
5368
|
+
["", "Examples:", " manni term get progressive-lens", ' manni term get "progressive lens" -f json'].join("\n")
|
|
5369
|
+
).action(async (term, paths, options) => {
|
|
5370
|
+
try {
|
|
5371
|
+
const format = formatChoice(options.format, GET_FORMATS);
|
|
5372
|
+
const found = await runGet({ ...await coreOptions(paths, options), term });
|
|
5373
|
+
print(format === "json" ? renderGetJson(found) : renderGetPretty(found, { color: colorFor2(options) }));
|
|
5374
|
+
process.exitCode = 0;
|
|
5375
|
+
} catch (err) {
|
|
5376
|
+
fail(err);
|
|
5377
|
+
}
|
|
5378
|
+
});
|
|
5379
|
+
withInputs(
|
|
5380
|
+
program.command("check").description("Check the set and every reference into it").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
|
|
5381
|
+
).option("-f, --format <format>", `output: ${FINDING_FORMATS.join(" | ")}`, "pretty").option("--baseline", "record the findings on the first run; after that, fail only on new ones").addHelpText(
|
|
5382
|
+
"after",
|
|
5383
|
+
[
|
|
5384
|
+
"",
|
|
5385
|
+
"Examples:",
|
|
5386
|
+
" manni term check # every collection",
|
|
5387
|
+
" manni term check -f github # CI annotations",
|
|
5388
|
+
" manni term check --baseline # ramp in on an existing docset"
|
|
5389
|
+
].join("\n")
|
|
5390
|
+
).action(async (paths, options) => {
|
|
5391
|
+
try {
|
|
5392
|
+
const format = formatChoice(options.format, FINDING_FORMATS);
|
|
5393
|
+
const report = await runCheck3({
|
|
5394
|
+
...await coreOptions(paths, options),
|
|
5395
|
+
...options.baseline === true ? { baseline: true } : {}
|
|
5396
|
+
});
|
|
5397
|
+
renderFindings(report, format, options, true);
|
|
5398
|
+
} catch (err) {
|
|
5399
|
+
fail(err);
|
|
5400
|
+
}
|
|
5401
|
+
});
|
|
5402
|
+
withInputs(
|
|
5403
|
+
program.command("lint").description("Run Vale over the definitions").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
|
|
5404
|
+
).option("-f, --format <format>", `output: ${FINDING_FORMATS.join(" | ")}`, "pretty").addHelpText(
|
|
5405
|
+
"after",
|
|
5406
|
+
["", "Examples:", " manni term lint", " manni term lint -f github"].join("\n")
|
|
5407
|
+
).action(async (paths, options) => {
|
|
5408
|
+
try {
|
|
5409
|
+
const format = formatChoice(options.format, FINDING_FORMATS);
|
|
5410
|
+
const report = await runLint(await coreOptions(paths, options));
|
|
5411
|
+
renderFindings(report, format, options, false);
|
|
5412
|
+
} catch (err) {
|
|
5413
|
+
fail(err);
|
|
5414
|
+
}
|
|
5415
|
+
});
|
|
5416
|
+
withInputs(
|
|
5417
|
+
program.command("write").description("Write the set back, or render it elsewhere").argument("[paths...]", "files, directories, or globs to read (use - for stdin)")
|
|
5418
|
+
).option("-f, --format <format>", `render as: ${writeFormats().join(" | ")} (default: each entry's own, in place)`).option("-o, --out <path>", "file or directory to render into; a trailing / means a directory").option("--check", "write nothing; exit 1 when the target differs from the render").option("--dry-run", "write nothing; print what would change").addHelpText(
|
|
5419
|
+
"after",
|
|
5420
|
+
[
|
|
5421
|
+
"",
|
|
5422
|
+
"Examples:",
|
|
5423
|
+
" manni term write # each entry back to its source",
|
|
5424
|
+
" manni term write -f json -o build/terms.json",
|
|
5425
|
+
" manni term write -f vale # into Vale's styles directory",
|
|
5426
|
+
" manni term write -f vale --check # keep the style in step"
|
|
5427
|
+
].join("\n")
|
|
5428
|
+
).action(async (paths, options) => {
|
|
5429
|
+
try {
|
|
5430
|
+
const report = await runWrite({
|
|
5431
|
+
...await coreOptions(paths, options),
|
|
5432
|
+
...options.format === void 0 ? {} : { format: options.format },
|
|
5433
|
+
...options.out === void 0 ? {} : { out: options.out },
|
|
5434
|
+
...options.check === true ? { check: true } : {},
|
|
5435
|
+
...options.dryRun === true ? { dryRun: true } : {}
|
|
5436
|
+
});
|
|
5437
|
+
print(renderWritePretty(report));
|
|
5438
|
+
if (report.wiring !== void 0) process.stderr.write(`${renderValeWiring(report.wiring)}
|
|
5439
|
+
`);
|
|
5440
|
+
process.exitCode = report.check && report.changes.length > 0 ? 1 : 0;
|
|
5441
|
+
} catch (err) {
|
|
5442
|
+
fail(err);
|
|
5443
|
+
}
|
|
5444
|
+
});
|
|
5445
|
+
program.command("formats").description("List the constructs read and written, per format").option("-f, --format <format>", `output: ${FORMATS_FORMATS.join(" | ")}`, "pretty").action((options) => {
|
|
5446
|
+
try {
|
|
5447
|
+
const format = formatChoice(options.format, FORMATS_FORMATS);
|
|
5448
|
+
const rows = listFormats();
|
|
5449
|
+
print(format === "json" ? renderFormatsJson(rows) : renderFormatsPretty(rows));
|
|
5450
|
+
process.exitCode = 0;
|
|
5451
|
+
} catch (err) {
|
|
5452
|
+
fail(err);
|
|
5453
|
+
}
|
|
5454
|
+
});
|
|
5455
|
+
return program;
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
// src/cli.ts
|
|
5459
|
+
function buildProgram6() {
|
|
5460
|
+
const meta = buildProgram().name("meta").description(
|
|
5461
|
+
"Validate, read, query and fill document metadata against JSON Schema"
|
|
5462
|
+
);
|
|
5463
|
+
const metaCommands = new Set(meta.commands.map((c) => c.name()));
|
|
5464
|
+
const program = new Command5();
|
|
5465
|
+
program.name("manni").description("Tools for documentation that is meant to be checked.").version(package_default.version, "-V, --version").showHelpAfterError("(add --help for usage)").exitOverride().configureOutput({
|
|
5466
|
+
outputError: (str, write) => {
|
|
5467
|
+
write(str);
|
|
5468
|
+
const m = /unknown command '([^']+)'/.exec(str);
|
|
5469
|
+
const name = m?.[1];
|
|
5470
|
+
if (name !== void 0 && metaCommands.has(name)) {
|
|
5471
|
+
write(
|
|
5472
|
+
`(\`${name}\` is a metadata command: try \`manni meta ${name}\`)
|
|
5473
|
+
`
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
});
|
|
5478
|
+
program.addCommand(meta);
|
|
5479
|
+
program.addCommand(
|
|
5480
|
+
buildProgram2().name("a11y").description(
|
|
5481
|
+
"Crawl a site and check every page for accessibility violations with axe-core"
|
|
5482
|
+
)
|
|
5483
|
+
);
|
|
5484
|
+
program.addCommand(
|
|
5485
|
+
buildProgram3().name("cite").description(
|
|
5486
|
+
"Track citations from doc claims to source lines and check them for drift"
|
|
5487
|
+
)
|
|
5488
|
+
);
|
|
5489
|
+
program.addCommand(
|
|
5490
|
+
buildProgram5().name("term").description("Check, lint and render a docset's terms and the references into them")
|
|
1487
5491
|
);
|
|
1488
5492
|
program.addCommand(
|
|
1489
5493
|
buildProgram4().name("key").description("Set and rotate the family key that encrypted values are encrypted with.")
|
|
1490
5494
|
);
|
|
1491
5495
|
return program;
|
|
1492
5496
|
}
|
|
1493
|
-
runIfMain(import.meta.url,
|
|
5497
|
+
runIfMain(import.meta.url, buildProgram6);
|
|
1494
5498
|
export {
|
|
1495
|
-
|
|
5499
|
+
buildProgram6 as buildProgram
|
|
1496
5500
|
};
|
|
1497
5501
|
//# sourceMappingURL=cli.js.map
|