lens-content-processor 0.64.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,157 @@
1
+ import { lensCardsIn } from "./lens-cards.js";
2
+ const NAME = "require_x_optional_lenses";
3
+ // A container fence: three or more colons, then a name (opener) or nothing
4
+ // (closer). A closer closes the innermost container whose fence is no
5
+ // longer than its own, as remark-directive reads it.
6
+ const FENCE_RE = /^\s*(:{3,})\s*([\w-]*)(\{[^}]*\})?\s*$/;
7
+ const X_ATTR_RE = /(?:^|[\s{])x\s*=\s*"?([^"\s}]*)"?/;
8
+ /** Each require_x_optional_lenses list in a text: its attributes and body
9
+ * (nested containers, such as a :::hide, stay inside the body). */
10
+ function listsIn(text) {
11
+ const lists = [];
12
+ const stack = [];
13
+ for (const line of text.split("\n")) {
14
+ const fence = line.match(FENCE_RE);
15
+ const open = stack.find((s) => s.list)?.list;
16
+ if (fence && fence[2]) {
17
+ stack.push({
18
+ fence: fence[1].length,
19
+ list: fence[2] === NAME && !open
20
+ ? { attrs: fence[3] ?? "", body: [] }
21
+ : undefined,
22
+ });
23
+ if (open)
24
+ open.body.push(line);
25
+ continue;
26
+ }
27
+ if (fence) {
28
+ let at = stack.length - 1;
29
+ while (at >= 0 && stack[at].fence > fence[1].length)
30
+ at -= 1;
31
+ if (at !== -1) {
32
+ const closed = stack.splice(at);
33
+ const list = closed.find((s) => s.list)?.list;
34
+ if (list)
35
+ lists.push({ attrs: list.attrs, body: list.body.join("\n") });
36
+ else if (open)
37
+ open.body.push(line);
38
+ continue;
39
+ }
40
+ }
41
+ if (open)
42
+ open.body.push(line);
43
+ }
44
+ return lists;
45
+ }
46
+ /**
47
+ * Record every `:::require_x_optional_lenses` list on its section and check
48
+ * it: x a positive whole number no larger than the list; every card a lens
49
+ * of the same (sub)module, other than the lens itself, listed once; a
50
+ * required lens in the list warns, since it would stand in for an optional
51
+ * one; and a list on an optional lens warns, since optional lenses never
52
+ * gate. Same (sub)module only, so the page knows every listed lens's
53
+ * completion (a sibling submodule's is not loaded there).
54
+ */
55
+ export function collectOptionalLensSets(modules) {
56
+ const errors = [];
57
+ for (const mod of modules) {
58
+ if (mod.slug.startsWith("lens/"))
59
+ continue;
60
+ const inModule = new Map();
61
+ for (const section of mod.sections) {
62
+ if (section.contentId)
63
+ inModule.set(section.contentId, section);
64
+ }
65
+ for (const section of mod.sections) {
66
+ const file = section.sourcePath ?? mod.sourcePath ?? `module:${mod.slug}`;
67
+ const sets = [];
68
+ section.segments.forEach((seg, segmentIndex) => {
69
+ if (seg.type !== "text" || !seg.content.includes(NAME))
70
+ return;
71
+ const lists = listsIn(seg.content);
72
+ if (lists.length === 0) {
73
+ errors.push({
74
+ file,
75
+ message: `"${NAME}" is not a list here: write it as a container, ':::${NAME}{x=2}' … ':::'`,
76
+ suggestion: `Put ':::${NAME}{x=2}' on its own line before the ::card lines, and ':::' on its own line after them`,
77
+ severity: "error",
78
+ });
79
+ }
80
+ for (const list of lists) {
81
+ const set = checkList(list, segmentIndex, file, section, inModule, mod.slug, errors);
82
+ if (set)
83
+ sets.push(set);
84
+ }
85
+ });
86
+ if (sets.length === 0)
87
+ continue;
88
+ section.optionalLensSets = sets;
89
+ if (section.optional) {
90
+ errors.push({
91
+ file,
92
+ message: `":::${NAME}" is on the optional lens "${section.meta?.title ?? section.contentId}", which never gates`,
93
+ suggestion: "Put the list on a required lens, or remove optional:: true from this lens in the module file",
94
+ severity: "warning",
95
+ });
96
+ }
97
+ }
98
+ }
99
+ return errors;
100
+ }
101
+ function checkList(list, segmentIndex, file, holder, inModule, moduleSlug, errors) {
102
+ const contentIds = [];
103
+ for (const card of lensCardsIn(list.body)) {
104
+ const name = card.title ?? card.contentId;
105
+ const target = card.contentId ? inModule.get(card.contentId) : undefined;
106
+ let problem = null;
107
+ if (card.targetType !== "lens" || !target) {
108
+ problem = `points to "${name}", which is not a lens of "${moduleSlug}"`;
109
+ }
110
+ else if (target === holder) {
111
+ problem = `points to "${name}", the lens that holds the list`;
112
+ }
113
+ else if (contentIds.includes(card.contentId)) {
114
+ problem = `lists "${name}" twice`;
115
+ }
116
+ if (problem) {
117
+ errors.push({
118
+ file,
119
+ message: `A card in ":::${NAME}" ${problem}`,
120
+ suggestion: "List each optional lens of this part once ({allow-external} does not apply here)",
121
+ severity: "error",
122
+ });
123
+ continue;
124
+ }
125
+ if (!target.optional) {
126
+ errors.push({
127
+ file,
128
+ message: `The card "${name}" in ":::${NAME}" is a required lens, so it would count toward the optional ones`,
129
+ suggestion: "Mark that lens optional:: true in the module file, or take it out of the list",
130
+ severity: "warning",
131
+ });
132
+ }
133
+ contentIds.push(card.contentId);
134
+ }
135
+ const raw = list.attrs.match(X_ATTR_RE)?.[1] ?? "";
136
+ const x = /^\d+$/.test(raw) ? parseInt(raw, 10) : 0;
137
+ if (x < 1) {
138
+ errors.push({
139
+ file,
140
+ message: `":::${NAME}" needs x as a positive number${raw ? `, got '${raw}'` : ""}`,
141
+ suggestion: `Write it as ':::${NAME}{x=2}'`,
142
+ severity: "error",
143
+ });
144
+ return null;
145
+ }
146
+ if (x > contentIds.length) {
147
+ errors.push({
148
+ file,
149
+ message: `":::${NAME}" asks for ${x}, but lists ${contentIds.length} lens${contentIds.length === 1 ? "" : "es"}`,
150
+ suggestion: "Lower x, or add ::card lines for more optional lenses",
151
+ severity: "error",
152
+ });
153
+ return null;
154
+ }
155
+ return { x, contentIds, segmentIndex };
156
+ }
157
+ //# sourceMappingURL=optional-lens-sets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optional-lens-sets.js","sourceRoot":"","sources":["../../src/validator/optional-lens-sets.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lens-content-processor",
3
- "version": "0.64.0",
3
+ "version": "0.66.0",
4
4
  "description": "Parser and validator for Lens educational content (Markdown modules, lenses, learning outcomes)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",