lens-content-processor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundler/article.d.ts +41 -0
- package/dist/bundler/article.js +240 -0
- package/dist/bundler/article.js.map +1 -0
- package/dist/bundler/article.test.d.ts +1 -0
- package/dist/bundler/article.test.js +212 -0
- package/dist/bundler/article.test.js.map +1 -0
- package/dist/bundler/video.d.ts +50 -0
- package/dist/bundler/video.js +213 -0
- package/dist/bundler/video.js.map +1 -0
- package/dist/bundler/video.test.d.ts +1 -0
- package/dist/bundler/video.test.js +95 -0
- package/dist/bundler/video.test.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +80 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +45 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/flattener/index.d.ts +20 -0
- package/dist/flattener/index.js +707 -0
- package/dist/flattener/index.js.map +1 -0
- package/dist/flattener/index.test.d.ts +1 -0
- package/dist/flattener/index.test.js +547 -0
- package/dist/flattener/index.test.js.map +1 -0
- package/dist/fs/read-vault.d.ts +4 -0
- package/dist/fs/read-vault.js +28 -0
- package/dist/fs/read-vault.js.map +1 -0
- package/dist/fs/read-vault.test.d.ts +1 -0
- package/dist/fs/read-vault.test.js +37 -0
- package/dist/fs/read-vault.test.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/course.d.ts +17 -0
- package/dist/parser/course.js +145 -0
- package/dist/parser/course.js.map +1 -0
- package/dist/parser/course.test.d.ts +1 -0
- package/dist/parser/course.test.js +117 -0
- package/dist/parser/course.test.js.map +1 -0
- package/dist/parser/frontmatter.d.ts +8 -0
- package/dist/parser/frontmatter.js +58 -0
- package/dist/parser/frontmatter.js.map +1 -0
- package/dist/parser/frontmatter.test.d.ts +1 -0
- package/dist/parser/frontmatter.test.js +54 -0
- package/dist/parser/frontmatter.test.js.map +1 -0
- package/dist/parser/learning-outcome.d.ts +21 -0
- package/dist/parser/learning-outcome.js +116 -0
- package/dist/parser/learning-outcome.js.map +1 -0
- package/dist/parser/learning-outcome.test.d.ts +1 -0
- package/dist/parser/learning-outcome.test.js +75 -0
- package/dist/parser/learning-outcome.test.js.map +1 -0
- package/dist/parser/lens.d.ts +51 -0
- package/dist/parser/lens.js +328 -0
- package/dist/parser/lens.js.map +1 -0
- package/dist/parser/lens.test.d.ts +1 -0
- package/dist/parser/lens.test.js +453 -0
- package/dist/parser/lens.test.js.map +1 -0
- package/dist/parser/module.d.ts +21 -0
- package/dist/parser/module.js +128 -0
- package/dist/parser/module.js.map +1 -0
- package/dist/parser/module.test.d.ts +1 -0
- package/dist/parser/module.test.js +99 -0
- package/dist/parser/module.test.js.map +1 -0
- package/dist/parser/sections.d.ts +18 -0
- package/dist/parser/sections.js +128 -0
- package/dist/parser/sections.js.map +1 -0
- package/dist/parser/sections.test.d.ts +1 -0
- package/dist/parser/sections.test.js +297 -0
- package/dist/parser/sections.test.js.map +1 -0
- package/dist/parser/wikilink.d.ts +12 -0
- package/dist/parser/wikilink.js +35 -0
- package/dist/parser/wikilink.js.map +1 -0
- package/dist/parser/wikilink.test.d.ts +1 -0
- package/dist/parser/wikilink.test.js +76 -0
- package/dist/parser/wikilink.test.js.map +1 -0
- package/dist/validator/field-typos.d.ts +10 -0
- package/dist/validator/field-typos.js +96 -0
- package/dist/validator/field-typos.js.map +1 -0
- package/dist/validator/field-typos.test.d.ts +1 -0
- package/dist/validator/field-typos.test.js +41 -0
- package/dist/validator/field-typos.test.js.map +1 -0
- package/dist/validator/field-values.d.ts +11 -0
- package/dist/validator/field-values.js +37 -0
- package/dist/validator/field-values.js.map +1 -0
- package/dist/validator/field-values.test.d.ts +1 -0
- package/dist/validator/field-values.test.js +45 -0
- package/dist/validator/field-values.test.js.map +1 -0
- package/dist/validator/segment-fields.d.ts +12 -0
- package/dist/validator/segment-fields.js +55 -0
- package/dist/validator/segment-fields.js.map +1 -0
- package/dist/validator/segment-fields.test.d.ts +1 -0
- package/dist/validator/segment-fields.test.js +73 -0
- package/dist/validator/segment-fields.test.js.map +1 -0
- package/dist/validator/standalone.test.d.ts +1 -0
- package/dist/validator/standalone.test.js +190 -0
- package/dist/validator/standalone.test.js.map +1 -0
- package/dist/validator/uuid.d.ts +17 -0
- package/dist/validator/uuid.js +46 -0
- package/dist/validator/uuid.js.map +1 -0
- package/dist/validator/uuid.test.d.ts +1 -0
- package/dist/validator/uuid.test.js +229 -0
- package/dist/validator/uuid.test.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/fs/read-vault.test.ts
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { readVaultFiles } from './read-vault.js';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
6
|
+
import { tmpdir } from 'os';
|
|
7
|
+
describe('readVaultFiles', () => {
|
|
8
|
+
it('reads all .md files from fixture directory', async () => {
|
|
9
|
+
const fixturesDir = join(import.meta.dirname, '../../fixtures/valid/minimal-module/input');
|
|
10
|
+
const files = await readVaultFiles(fixturesDir);
|
|
11
|
+
expect(files.has('modules/intro.md')).toBe(true);
|
|
12
|
+
expect(files.get('modules/intro.md')).toContain('slug: intro');
|
|
13
|
+
});
|
|
14
|
+
it('reads files from nested directories with correct relative paths', async () => {
|
|
15
|
+
const fixturesDir = join(import.meta.dirname, '../../fixtures/golden/actual-content/input');
|
|
16
|
+
const files = await readVaultFiles(fixturesDir);
|
|
17
|
+
// Should have files in nested paths like "modules/intro.md", "Lenses/..."
|
|
18
|
+
const paths = Array.from(files.keys());
|
|
19
|
+
expect(paths.some(p => p.includes('/'))).toBe(true);
|
|
20
|
+
// Paths should NOT start with leading slash
|
|
21
|
+
expect(paths.every(p => !p.startsWith('/'))).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it('returns empty map for directory with no .md files', async () => {
|
|
24
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'empty-vault-'));
|
|
25
|
+
try {
|
|
26
|
+
const files = await readVaultFiles(tempDir);
|
|
27
|
+
expect(files.size).toBe(0);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
rmSync(tempDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('throws descriptive error for non-existent directory', async () => {
|
|
34
|
+
await expect(readVaultFiles('/nonexistent/path')).rejects.toThrow(/ENOENT|no such file/i);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=read-vault.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-vault.test.js","sourceRoot":"","sources":["../../src/fs/read-vault.test.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAE5B,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,2CAA2C,CAAC,CAAC;QAE3F,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;QAEhD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,4CAA4C,CAAC,CAAC;QAE5F,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;QAEhD,0EAA0E;QAC1E,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpD,4CAA4C;QAC5C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,MAAM,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface ProcessResult {
|
|
2
|
+
modules: FlattenedModule[];
|
|
3
|
+
courses: Course[];
|
|
4
|
+
errors: ContentError[];
|
|
5
|
+
}
|
|
6
|
+
export interface FlattenedModule {
|
|
7
|
+
slug: string;
|
|
8
|
+
title: string;
|
|
9
|
+
contentId: string | null;
|
|
10
|
+
sections: Section[];
|
|
11
|
+
error?: string;
|
|
12
|
+
warnings?: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface Course {
|
|
15
|
+
slug: string;
|
|
16
|
+
title: string;
|
|
17
|
+
progression: ProgressionItem[];
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface Section {
|
|
21
|
+
type: 'page' | 'lens-video' | 'lens-article';
|
|
22
|
+
meta: SectionMeta;
|
|
23
|
+
segments: Segment[];
|
|
24
|
+
optional?: boolean;
|
|
25
|
+
contentId: string | null;
|
|
26
|
+
learningOutcomeId: string | null;
|
|
27
|
+
videoId: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface SectionMeta {
|
|
30
|
+
title?: string;
|
|
31
|
+
author?: string;
|
|
32
|
+
sourceUrl?: string;
|
|
33
|
+
channel?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ProgressionItem {
|
|
36
|
+
type: 'module' | 'meeting';
|
|
37
|
+
slug?: string;
|
|
38
|
+
number?: number;
|
|
39
|
+
optional?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ContentError {
|
|
42
|
+
file: string;
|
|
43
|
+
line?: number;
|
|
44
|
+
message: string;
|
|
45
|
+
suggestion?: string;
|
|
46
|
+
severity: 'error' | 'warning';
|
|
47
|
+
}
|
|
48
|
+
export interface TextSegment {
|
|
49
|
+
type: 'text';
|
|
50
|
+
content: string;
|
|
51
|
+
optional?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface ChatSegment {
|
|
54
|
+
type: 'chat';
|
|
55
|
+
instructions?: string;
|
|
56
|
+
hidePreviousContentFromUser?: boolean;
|
|
57
|
+
hidePreviousContentFromTutor?: boolean;
|
|
58
|
+
optional?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface ArticleExcerptSegment {
|
|
61
|
+
type: 'article-excerpt';
|
|
62
|
+
content: string;
|
|
63
|
+
collapsed_before?: string;
|
|
64
|
+
collapsed_after?: string;
|
|
65
|
+
optional?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface VideoExcerptSegment {
|
|
68
|
+
type: 'video-excerpt';
|
|
69
|
+
from: number;
|
|
70
|
+
to: number | null;
|
|
71
|
+
transcript: string;
|
|
72
|
+
optional?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export type Segment = TextSegment | ChatSegment | ArticleExcerptSegment | VideoExcerptSegment;
|
|
75
|
+
export declare function processContent(files: Map<string, string>): ProcessResult;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { flattenModule } from './flattener/index.js';
|
|
2
|
+
import { parseCourse } from './parser/course.js';
|
|
3
|
+
import { parseLearningOutcome } from './parser/learning-outcome.js';
|
|
4
|
+
import { parseLens } from './parser/lens.js';
|
|
5
|
+
import { parseWikilink, resolveWikilinkPath, findFileWithExtension } from './parser/wikilink.js';
|
|
6
|
+
import { validateUuids } from './validator/uuid.js';
|
|
7
|
+
import { extractArticleExcerpt } from './bundler/article.js';
|
|
8
|
+
import { extractVideoExcerpt } from './bundler/video.js';
|
|
9
|
+
/**
|
|
10
|
+
* Validate lens excerpts by checking if source files exist and anchors/timestamps are valid.
|
|
11
|
+
*/
|
|
12
|
+
function validateLensExcerpts(lens, lensPath, files) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
for (const section of lens.sections) {
|
|
15
|
+
// Skip sections without source (e.g., Text sections)
|
|
16
|
+
if (!section.source)
|
|
17
|
+
continue;
|
|
18
|
+
// Resolve the source wikilink to get the actual file path
|
|
19
|
+
const wikilink = parseWikilink(section.source);
|
|
20
|
+
if (!wikilink)
|
|
21
|
+
continue;
|
|
22
|
+
const resolvedPath = resolveWikilinkPath(wikilink.path, lensPath);
|
|
23
|
+
const actualPath = findFileWithExtension(resolvedPath, files);
|
|
24
|
+
if (!actualPath) {
|
|
25
|
+
errors.push({
|
|
26
|
+
file: lensPath,
|
|
27
|
+
line: section.line,
|
|
28
|
+
message: `Source file not found: ${resolvedPath}`,
|
|
29
|
+
suggestion: 'Check that the file exists and the path is correct',
|
|
30
|
+
severity: 'error',
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const sourceContent = files.get(actualPath);
|
|
35
|
+
// Validate article excerpts
|
|
36
|
+
if (section.type === 'article' || section.type === 'lens-article') {
|
|
37
|
+
for (const segment of section.segments) {
|
|
38
|
+
if (segment.type === 'article-excerpt') {
|
|
39
|
+
const result = extractArticleExcerpt(sourceContent, segment.fromAnchor, segment.toAnchor, actualPath);
|
|
40
|
+
if (result.error) {
|
|
41
|
+
errors.push({ ...result.error, file: lensPath });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Validate video excerpts
|
|
47
|
+
if (section.type === 'video' || section.type === 'lens-video') {
|
|
48
|
+
// Look for corresponding .timestamps.json file
|
|
49
|
+
const timestampsPath = actualPath.replace(/\.md$/, '.timestamps.json');
|
|
50
|
+
let timestamps;
|
|
51
|
+
if (files.has(timestampsPath)) {
|
|
52
|
+
try {
|
|
53
|
+
timestamps = JSON.parse(files.get(timestampsPath));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// JSON parse error - will fall back to inline timestamps
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const segment of section.segments) {
|
|
60
|
+
if (segment.type === 'video-excerpt') {
|
|
61
|
+
const result = extractVideoExcerpt(sourceContent, segment.fromTimeStr, segment.toTimeStr, actualPath, timestamps);
|
|
62
|
+
if (result.error) {
|
|
63
|
+
errors.push({ ...result.error, file: lensPath });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return errors;
|
|
70
|
+
}
|
|
71
|
+
export function processContent(files) {
|
|
72
|
+
const modules = [];
|
|
73
|
+
const courses = [];
|
|
74
|
+
const errors = [];
|
|
75
|
+
const uuidEntries = [];
|
|
76
|
+
// Identify file types by path
|
|
77
|
+
for (const [path, content] of files.entries()) {
|
|
78
|
+
if (path.startsWith('modules/')) {
|
|
79
|
+
const result = flattenModule(path, files);
|
|
80
|
+
if (result.module) {
|
|
81
|
+
modules.push(result.module);
|
|
82
|
+
// Collect module contentId for UUID validation
|
|
83
|
+
if (result.module.contentId) {
|
|
84
|
+
uuidEntries.push({
|
|
85
|
+
uuid: result.module.contentId,
|
|
86
|
+
file: path,
|
|
87
|
+
field: 'contentId',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
errors.push(...result.errors);
|
|
92
|
+
}
|
|
93
|
+
else if (path.startsWith('courses/')) {
|
|
94
|
+
const result = parseCourse(content, path);
|
|
95
|
+
if (result.course) {
|
|
96
|
+
courses.push(result.course);
|
|
97
|
+
}
|
|
98
|
+
errors.push(...result.errors);
|
|
99
|
+
}
|
|
100
|
+
else if (path.startsWith('Learning Outcomes/') || path.includes('/Learning Outcomes/')) {
|
|
101
|
+
// Fully validate Learning Outcome (structure, fields, wikilink syntax)
|
|
102
|
+
const result = parseLearningOutcome(content, path);
|
|
103
|
+
errors.push(...result.errors);
|
|
104
|
+
// Collect id for UUID validation
|
|
105
|
+
if (result.learningOutcome?.id) {
|
|
106
|
+
uuidEntries.push({
|
|
107
|
+
uuid: result.learningOutcome.id,
|
|
108
|
+
file: path,
|
|
109
|
+
field: 'id',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (path.startsWith('Lenses/') || path.includes('/Lenses/')) {
|
|
114
|
+
// Fully validate Lens (structure, segments, fields)
|
|
115
|
+
const result = parseLens(content, path);
|
|
116
|
+
errors.push(...result.errors);
|
|
117
|
+
// Validate excerpts (source files exist, anchors/timestamps valid)
|
|
118
|
+
if (result.lens) {
|
|
119
|
+
const excerptErrors = validateLensExcerpts(result.lens, path, files);
|
|
120
|
+
errors.push(...excerptErrors);
|
|
121
|
+
}
|
|
122
|
+
// Collect id for UUID validation
|
|
123
|
+
if (result.lens?.id) {
|
|
124
|
+
uuidEntries.push({
|
|
125
|
+
uuid: result.lens.id,
|
|
126
|
+
file: path,
|
|
127
|
+
field: 'id',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Validate all collected UUIDs
|
|
133
|
+
const uuidValidation = validateUuids(uuidEntries);
|
|
134
|
+
errors.push(...uuidValidation.errors);
|
|
135
|
+
return { modules, courses, errors };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ContentError, Course } from '../index.js';
|
|
2
|
+
export declare const COURSE_SECTION_TYPES: Set<string>;
|
|
3
|
+
export interface CourseParseResult {
|
|
4
|
+
course: Course | null;
|
|
5
|
+
errors: ContentError[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parses a course file and extracts its structure.
|
|
9
|
+
*
|
|
10
|
+
* Course files use H1 sections:
|
|
11
|
+
* - `# Module: [[../modules/name.md|Display]]` - references a module
|
|
12
|
+
* - `# Meeting: 1` - marks a meeting point
|
|
13
|
+
*
|
|
14
|
+
* The module slug is extracted from the filename in the wikilink path.
|
|
15
|
+
* Reference validation (checking if modules exist) happens in processContent.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseCourse(content: string, file: string): CourseParseResult;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { parseFrontmatter } from './frontmatter.js';
|
|
2
|
+
import { parseSections } from './sections.js';
|
|
3
|
+
import { parseWikilink } from './wikilink.js';
|
|
4
|
+
import { basename } from 'path';
|
|
5
|
+
// Valid section types for course files
|
|
6
|
+
export const COURSE_SECTION_TYPES = new Set(['module', 'meeting']);
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the slug from a module path (filename without extension)
|
|
9
|
+
* e.g., '../modules/intro.md' -> 'intro'
|
|
10
|
+
*/
|
|
11
|
+
function extractSlugFromPath(path) {
|
|
12
|
+
const filename = basename(path);
|
|
13
|
+
return filename.replace(/\.md$/, '');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parses a Module section title to extract the wikilink and module slug
|
|
17
|
+
* Title format: [[../modules/intro.md|Display Text]]
|
|
18
|
+
*/
|
|
19
|
+
function parseModuleSection(section, file) {
|
|
20
|
+
// The title should be a wikilink
|
|
21
|
+
const wikilink = parseWikilink(section.title);
|
|
22
|
+
if (!wikilink) {
|
|
23
|
+
return {
|
|
24
|
+
error: {
|
|
25
|
+
file,
|
|
26
|
+
line: section.line,
|
|
27
|
+
message: `Module section must have a wikilink in title, got: "${section.title}"`,
|
|
28
|
+
suggestion: 'Use format: # Module: [[../modules/module-name.md|Display Text]]',
|
|
29
|
+
severity: 'error',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const slug = extractSlugFromPath(wikilink.path);
|
|
34
|
+
const optional = section.fields.optional === 'true';
|
|
35
|
+
return { slug, optional };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parses a Meeting section title to extract the meeting number
|
|
39
|
+
* Title format: 1, 2, 3, etc.
|
|
40
|
+
*/
|
|
41
|
+
function parseMeetingSection(section, file) {
|
|
42
|
+
const meetingNumber = parseInt(section.title, 10);
|
|
43
|
+
if (isNaN(meetingNumber)) {
|
|
44
|
+
return {
|
|
45
|
+
error: {
|
|
46
|
+
file,
|
|
47
|
+
line: section.line,
|
|
48
|
+
message: `Meeting section must have a number in title, got: "${section.title}"`,
|
|
49
|
+
suggestion: 'Use format: # Meeting: 1',
|
|
50
|
+
severity: 'error',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { number: meetingNumber };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parses a course file and extracts its structure.
|
|
58
|
+
*
|
|
59
|
+
* Course files use H1 sections:
|
|
60
|
+
* - `# Module: [[../modules/name.md|Display]]` - references a module
|
|
61
|
+
* - `# Meeting: 1` - marks a meeting point
|
|
62
|
+
*
|
|
63
|
+
* The module slug is extracted from the filename in the wikilink path.
|
|
64
|
+
* Reference validation (checking if modules exist) happens in processContent.
|
|
65
|
+
*/
|
|
66
|
+
export function parseCourse(content, file) {
|
|
67
|
+
const errors = [];
|
|
68
|
+
// Parse frontmatter
|
|
69
|
+
const frontmatterResult = parseFrontmatter(content, file);
|
|
70
|
+
if (frontmatterResult.error) {
|
|
71
|
+
errors.push(frontmatterResult.error);
|
|
72
|
+
return { course: null, errors };
|
|
73
|
+
}
|
|
74
|
+
const { frontmatter, body, bodyStartLine } = frontmatterResult;
|
|
75
|
+
// Validate required frontmatter fields
|
|
76
|
+
if (!frontmatter.slug) {
|
|
77
|
+
errors.push({
|
|
78
|
+
file,
|
|
79
|
+
line: 2,
|
|
80
|
+
message: 'Missing required field: slug',
|
|
81
|
+
suggestion: "Add 'slug: your-course-slug' to frontmatter",
|
|
82
|
+
severity: 'error',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!frontmatter.title) {
|
|
86
|
+
errors.push({
|
|
87
|
+
file,
|
|
88
|
+
line: 2,
|
|
89
|
+
message: 'Missing required field: title',
|
|
90
|
+
suggestion: "Add 'title: Your Course Title' to frontmatter",
|
|
91
|
+
severity: 'error',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (errors.length > 0) {
|
|
95
|
+
return { course: null, errors };
|
|
96
|
+
}
|
|
97
|
+
// Parse sections (H1 headers for course files)
|
|
98
|
+
const sectionsResult = parseSections(body, 1, COURSE_SECTION_TYPES, file);
|
|
99
|
+
// Adjust line numbers to account for frontmatter
|
|
100
|
+
for (const error of sectionsResult.errors) {
|
|
101
|
+
if (error.line) {
|
|
102
|
+
error.line += bodyStartLine - 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
errors.push(...sectionsResult.errors);
|
|
106
|
+
for (const section of sectionsResult.sections) {
|
|
107
|
+
section.line += bodyStartLine - 1;
|
|
108
|
+
}
|
|
109
|
+
// Build progression array from parsed sections
|
|
110
|
+
const progression = [];
|
|
111
|
+
for (const section of sectionsResult.sections) {
|
|
112
|
+
if (section.type === 'module') {
|
|
113
|
+
const result = parseModuleSection(section, file);
|
|
114
|
+
if ('error' in result) {
|
|
115
|
+
errors.push(result.error);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
progression.push({
|
|
119
|
+
type: 'module',
|
|
120
|
+
slug: result.slug,
|
|
121
|
+
optional: result.optional,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (section.type === 'meeting') {
|
|
126
|
+
const result = parseMeetingSection(section, file);
|
|
127
|
+
if ('error' in result) {
|
|
128
|
+
errors.push(result.error);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
progression.push({
|
|
132
|
+
type: 'meeting',
|
|
133
|
+
number: result.number,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const course = {
|
|
139
|
+
slug: frontmatter.slug,
|
|
140
|
+
title: frontmatter.title,
|
|
141
|
+
progression,
|
|
142
|
+
};
|
|
143
|
+
return { course, errors };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=course.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course.js","sourceRoot":"","sources":["../../src/parser/course.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/parser/course.test.ts
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { parseCourse } from './course.js';
|
|
4
|
+
describe('parseCourse', () => {
|
|
5
|
+
it('parses course with module references', () => {
|
|
6
|
+
const content = `---
|
|
7
|
+
slug: intro-course
|
|
8
|
+
title: Introduction to AI Safety
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Module: [[../modules/intro.md|Introduction]]
|
|
12
|
+
|
|
13
|
+
# Module: [[../modules/advanced.md|Advanced Topics]]
|
|
14
|
+
optional:: true
|
|
15
|
+
|
|
16
|
+
# Meeting: 1
|
|
17
|
+
|
|
18
|
+
# Module: [[../modules/conclusion.md|Conclusion]]
|
|
19
|
+
`;
|
|
20
|
+
const result = parseCourse(content, 'courses/intro.md');
|
|
21
|
+
expect(result.course?.slug).toBe('intro-course');
|
|
22
|
+
expect(result.course?.title).toBe('Introduction to AI Safety');
|
|
23
|
+
expect(result.course?.progression).toHaveLength(4);
|
|
24
|
+
expect(result.course?.progression[0].type).toBe('module');
|
|
25
|
+
expect(result.course?.progression[0].slug).toBe('intro');
|
|
26
|
+
expect(result.course?.progression[0].optional).toBe(false);
|
|
27
|
+
expect(result.course?.progression[1].type).toBe('module');
|
|
28
|
+
expect(result.course?.progression[1].slug).toBe('advanced');
|
|
29
|
+
expect(result.course?.progression[1].optional).toBe(true);
|
|
30
|
+
expect(result.course?.progression[2].type).toBe('meeting');
|
|
31
|
+
expect(result.course?.progression[2].number).toBe(1);
|
|
32
|
+
expect(result.course?.progression[3].type).toBe('module');
|
|
33
|
+
expect(result.course?.progression[3].slug).toBe('conclusion');
|
|
34
|
+
expect(result.errors).toHaveLength(0);
|
|
35
|
+
});
|
|
36
|
+
it('validates module references exist', () => {
|
|
37
|
+
const content = `---
|
|
38
|
+
slug: broken-course
|
|
39
|
+
title: Broken Course
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# Module: [[../modules/nonexistent.md|Missing]]
|
|
43
|
+
`;
|
|
44
|
+
// Note: Reference validation happens in processContent, not parseCourse
|
|
45
|
+
// parseCourse only parses the structure, it does not validate file existence
|
|
46
|
+
const result = parseCourse(content, 'courses/broken.md');
|
|
47
|
+
expect(result.course).toBeDefined();
|
|
48
|
+
expect(result.course?.slug).toBe('broken-course');
|
|
49
|
+
expect(result.course?.progression).toHaveLength(1);
|
|
50
|
+
expect(result.course?.progression[0].type).toBe('module');
|
|
51
|
+
expect(result.course?.progression[0].slug).toBe('nonexistent');
|
|
52
|
+
// Errors about missing modules are added during processContent, not parseCourse
|
|
53
|
+
});
|
|
54
|
+
it('handles missing frontmatter', () => {
|
|
55
|
+
const content = `# Module: [[../modules/intro.md|Introduction]]`;
|
|
56
|
+
const result = parseCourse(content, 'courses/bad.md');
|
|
57
|
+
expect(result.course).toBeNull();
|
|
58
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
59
|
+
expect(result.errors[0].message).toContain('frontmatter');
|
|
60
|
+
});
|
|
61
|
+
it('requires slug in frontmatter', () => {
|
|
62
|
+
const content = `---
|
|
63
|
+
title: Course Without Slug
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Module: [[../modules/intro.md|Introduction]]
|
|
67
|
+
`;
|
|
68
|
+
const result = parseCourse(content, 'courses/no-slug.md');
|
|
69
|
+
expect(result.course).toBeNull();
|
|
70
|
+
expect(result.errors.some(e => e.message.includes('slug'))).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
it('requires title in frontmatter', () => {
|
|
73
|
+
const content = `---
|
|
74
|
+
slug: no-title-course
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# Module: [[../modules/intro.md|Introduction]]
|
|
78
|
+
`;
|
|
79
|
+
const result = parseCourse(content, 'courses/no-title.md');
|
|
80
|
+
expect(result.course).toBeNull();
|
|
81
|
+
expect(result.errors.some(e => e.message.includes('title'))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('reports error for unknown section types', () => {
|
|
84
|
+
const content = `---
|
|
85
|
+
slug: test-course
|
|
86
|
+
title: Test Course
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Unknown: Bad Section
|
|
90
|
+
`;
|
|
91
|
+
const result = parseCourse(content, 'courses/unknown.md');
|
|
92
|
+
expect(result.errors.some(e => e.message.includes('Unknown section type'))).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it('reports error for Module section without wikilink', () => {
|
|
95
|
+
const content = `---
|
|
96
|
+
slug: test-course
|
|
97
|
+
title: Test Course
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Module: No Wikilink Here
|
|
101
|
+
`;
|
|
102
|
+
const result = parseCourse(content, 'courses/no-link.md');
|
|
103
|
+
expect(result.errors.some(e => e.message.includes('wikilink'))).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
it('reports error for Meeting section without number', () => {
|
|
106
|
+
const content = `---
|
|
107
|
+
slug: test-course
|
|
108
|
+
title: Test Course
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Meeting: not a number
|
|
112
|
+
`;
|
|
113
|
+
const result = parseCourse(content, 'courses/bad-meeting.md');
|
|
114
|
+
expect(result.errors.some(e => e.message.includes('number'))).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=course.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course.test.js","sourceRoot":"","sources":["../../src/parser/course.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ContentError } from '../index.js';
|
|
2
|
+
export interface FrontmatterResult {
|
|
3
|
+
frontmatter: Record<string, unknown>;
|
|
4
|
+
body: string;
|
|
5
|
+
bodyStartLine: number;
|
|
6
|
+
error?: ContentError;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseFrontmatter(content: string, file?: string): FrontmatterResult;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/parser/frontmatter.ts
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
// Matches: ---\n<yaml>\n---\n<body>
|
|
4
|
+
const FRONTMATTER_PATTERN = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
|
|
5
|
+
export function parseFrontmatter(content, file = '') {
|
|
6
|
+
const match = content.match(FRONTMATTER_PATTERN);
|
|
7
|
+
if (!match) {
|
|
8
|
+
// Check if it starts with --- but doesn't close
|
|
9
|
+
if (content.startsWith('---\n') && !content.includes('\n---\n')) {
|
|
10
|
+
return {
|
|
11
|
+
frontmatter: {},
|
|
12
|
+
body: content,
|
|
13
|
+
bodyStartLine: 1,
|
|
14
|
+
error: {
|
|
15
|
+
file,
|
|
16
|
+
line: 1,
|
|
17
|
+
message: 'Unclosed frontmatter - missing closing ---',
|
|
18
|
+
suggestion: 'Add --- on its own line after frontmatter fields',
|
|
19
|
+
severity: 'error',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
frontmatter: {},
|
|
25
|
+
body: content,
|
|
26
|
+
bodyStartLine: 1,
|
|
27
|
+
error: {
|
|
28
|
+
file,
|
|
29
|
+
line: 1,
|
|
30
|
+
message: 'Missing frontmatter',
|
|
31
|
+
suggestion: "Add YAML frontmatter with 'slug' and 'title' fields",
|
|
32
|
+
severity: 'error',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const yamlContent = match[1];
|
|
37
|
+
const body = match[2];
|
|
38
|
+
const bodyStartLine = yamlContent.split('\n').length + 3; // 1 for opening ---, N for yaml, 1 for closing ---
|
|
39
|
+
try {
|
|
40
|
+
const frontmatter = parseYaml(yamlContent) ?? {};
|
|
41
|
+
return { frontmatter, body, bodyStartLine };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return {
|
|
45
|
+
frontmatter: {},
|
|
46
|
+
body,
|
|
47
|
+
bodyStartLine,
|
|
48
|
+
error: {
|
|
49
|
+
file,
|
|
50
|
+
line: 2,
|
|
51
|
+
message: `Invalid YAML: ${e instanceof Error ? e.message : String(e)}`,
|
|
52
|
+
suggestion: 'Check YAML syntax - colons, indentation, quoting',
|
|
53
|
+
severity: 'error',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/parser/frontmatter.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAU1C,oCAAoC;AACpC,MAAM,mBAAmB,GAAG,oCAAoC,CAAC;AAEjE,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,OAAe,EAAE;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAEjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,gDAAgD;QAChD,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChE,OAAO;gBACL,WAAW,EAAE,EAAE;gBACf,IAAI,EAAE,OAAO;gBACb,aAAa,EAAE,CAAC;gBAChB,KAAK,EAAE;oBACL,IAAI;oBACJ,IAAI,EAAE,CAAC;oBACP,OAAO,EAAE,4CAA4C;oBACrD,UAAU,EAAE,kDAAkD;oBAC9D,QAAQ,EAAE,OAAO;iBAClB;aACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,WAAW,EAAE,EAAE;YACf,IAAI,EAAE,OAAO;YACb,aAAa,EAAE,CAAC;YAChB,KAAK,EAAE;gBACL,IAAI;gBACJ,IAAI,EAAE,CAAC;gBACP,OAAO,EAAE,qBAAqB;gBAC9B,UAAU,EAAE,qDAAqD;gBACjE,QAAQ,EAAE,OAAO;aAClB;SACF,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,mDAAmD;IAE7G,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACjD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC9C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO;YACL,WAAW,EAAE,EAAE;YACf,IAAI;YACJ,aAAa;YACb,KAAK,EAAE;gBACL,IAAI;gBACJ,IAAI,EAAE,CAAC;gBACP,OAAO,EAAE,iBAAiB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;gBACtE,UAAU,EAAE,kDAAkD;gBAC9D,QAAQ,EAAE,OAAO;aAClB;SACF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|