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,99 @@
|
|
|
1
|
+
// src/parser/module.test.ts
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { parseModule, parsePageTextSegments } from './module.js';
|
|
4
|
+
describe('parseModule', () => {
|
|
5
|
+
it('parses complete module', () => {
|
|
6
|
+
const content = `---
|
|
7
|
+
slug: intro
|
|
8
|
+
title: Introduction
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Learning Outcome: First Topic
|
|
12
|
+
source:: [[../Learning Outcomes/lo1.md|LO1]]
|
|
13
|
+
`;
|
|
14
|
+
const result = parseModule(content, 'modules/intro.md');
|
|
15
|
+
expect(result.module?.slug).toBe('intro');
|
|
16
|
+
expect(result.module?.title).toBe('Introduction');
|
|
17
|
+
expect(result.module?.sections).toHaveLength(1);
|
|
18
|
+
expect(result.errors).toHaveLength(0);
|
|
19
|
+
});
|
|
20
|
+
it('collects errors from all parsing stages', () => {
|
|
21
|
+
const content = `# No frontmatter
|
|
22
|
+
|
|
23
|
+
# Unknown: Bad Type
|
|
24
|
+
`;
|
|
25
|
+
const result = parseModule(content, 'modules/bad.md');
|
|
26
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
27
|
+
expect(result.errors.some(e => e.message.includes('frontmatter'))).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
it('parses Page section with ## Text content', () => {
|
|
30
|
+
const content = `---
|
|
31
|
+
slug: test
|
|
32
|
+
title: Test Module
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# Page: Welcome
|
|
36
|
+
id:: d1e2f3a4-5678-90ab-cdef-1234567890ab
|
|
37
|
+
|
|
38
|
+
## Text
|
|
39
|
+
content::
|
|
40
|
+
This is the welcome text.
|
|
41
|
+
It spans multiple lines.
|
|
42
|
+
`;
|
|
43
|
+
const result = parseModule(content, 'modules/test.md');
|
|
44
|
+
expect(result.module?.sections).toHaveLength(1);
|
|
45
|
+
expect(result.module?.sections[0].type).toBe('page');
|
|
46
|
+
expect(result.module?.sections[0].fields.id).toBe('d1e2f3a4-5678-90ab-cdef-1234567890ab');
|
|
47
|
+
// The body should contain the ## Text subsection
|
|
48
|
+
expect(result.module?.sections[0].body).toContain('## Text');
|
|
49
|
+
expect(result.module?.sections[0].body).toContain('content::');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('parsePageTextSegments', () => {
|
|
53
|
+
it('parses ## Text subsection with multiline content', () => {
|
|
54
|
+
const body = `id:: d1e2f3a4-5678-90ab-cdef-1234567890ab
|
|
55
|
+
|
|
56
|
+
## Text
|
|
57
|
+
content::
|
|
58
|
+
This is the welcome text.
|
|
59
|
+
It spans multiple lines.
|
|
60
|
+
`;
|
|
61
|
+
const segments = parsePageTextSegments(body);
|
|
62
|
+
expect(segments).toHaveLength(1);
|
|
63
|
+
expect(segments[0].type).toBe('text');
|
|
64
|
+
expect(segments[0].content).toContain('welcome text');
|
|
65
|
+
expect(segments[0].content).toContain('multiple lines');
|
|
66
|
+
});
|
|
67
|
+
it('parses multiple ## Text subsections', () => {
|
|
68
|
+
const body = `id:: some-id
|
|
69
|
+
|
|
70
|
+
## Text
|
|
71
|
+
content::
|
|
72
|
+
First text segment.
|
|
73
|
+
|
|
74
|
+
## Text
|
|
75
|
+
content::
|
|
76
|
+
Second text segment.
|
|
77
|
+
`;
|
|
78
|
+
const segments = parsePageTextSegments(body);
|
|
79
|
+
expect(segments).toHaveLength(2);
|
|
80
|
+
expect(segments[0].content).toContain('First text');
|
|
81
|
+
expect(segments[1].content).toContain('Second text');
|
|
82
|
+
});
|
|
83
|
+
it('returns empty array when no ## Text subsections', () => {
|
|
84
|
+
const body = `id:: some-id
|
|
85
|
+
no text subsections here
|
|
86
|
+
`;
|
|
87
|
+
const segments = parsePageTextSegments(body);
|
|
88
|
+
expect(segments).toHaveLength(0);
|
|
89
|
+
});
|
|
90
|
+
it('handles content:: on same line', () => {
|
|
91
|
+
const body = `## Text
|
|
92
|
+
content:: Single line content.
|
|
93
|
+
`;
|
|
94
|
+
const segments = parsePageTextSegments(body);
|
|
95
|
+
expect(segments).toHaveLength(1);
|
|
96
|
+
expect(segments[0].content).toBe('Single line content.');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=module.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.test.js","sourceRoot":"","sources":["../../src/parser/module.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ContentError } from '../index.js';
|
|
2
|
+
export interface ParsedSection {
|
|
3
|
+
type: string;
|
|
4
|
+
title: string;
|
|
5
|
+
rawType: string;
|
|
6
|
+
fields: Record<string, string>;
|
|
7
|
+
body: string;
|
|
8
|
+
line: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SectionsResult {
|
|
11
|
+
sections: ParsedSection[];
|
|
12
|
+
errors: ContentError[];
|
|
13
|
+
}
|
|
14
|
+
export declare const MODULE_SECTION_TYPES: Set<string>;
|
|
15
|
+
export declare const LO_SECTION_TYPES: Set<string>;
|
|
16
|
+
export declare const LENS_SECTION_TYPES: Set<string>;
|
|
17
|
+
export declare const LENS_OUTPUT_TYPE: Record<string, string>;
|
|
18
|
+
export declare function parseSections(content: string, headerLevel: 1 | 2 | 3 | 4, validTypes: Set<string>, file?: string): SectionsResult;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Valid section types per file type (exported for use by other parsers)
|
|
2
|
+
export const MODULE_SECTION_TYPES = new Set(['learning outcome', 'page', 'uncategorized']);
|
|
3
|
+
export const LO_SECTION_TYPES = new Set(['lens', 'test']);
|
|
4
|
+
// Lens sections: input headers are `### Article:`, `### Video:`, `### Text:`
|
|
5
|
+
// Output types are `lens-article`, `lens-video`, `text` (v2 format)
|
|
6
|
+
export const LENS_SECTION_TYPES = new Set(['text', 'article', 'video']);
|
|
7
|
+
// Map input section names to output types for Lens files
|
|
8
|
+
export const LENS_OUTPUT_TYPE = {
|
|
9
|
+
'text': 'text',
|
|
10
|
+
'article': 'lens-article',
|
|
11
|
+
'video': 'lens-video',
|
|
12
|
+
};
|
|
13
|
+
// Header pattern is parameterized by level (1-4)
|
|
14
|
+
function makeSectionPattern(level) {
|
|
15
|
+
const hashes = '#'.repeat(level);
|
|
16
|
+
// Match: ^#{level} <type>: <title>$
|
|
17
|
+
// Captures: group 1 = type, group 2 = title
|
|
18
|
+
return new RegExp(`^${hashes}\\s+([^:]+):\\s*(.*)$`, 'i');
|
|
19
|
+
}
|
|
20
|
+
export function parseSections(content, headerLevel, validTypes, file = '') {
|
|
21
|
+
const SECTION_HEADER_PATTERN = makeSectionPattern(headerLevel);
|
|
22
|
+
const lines = content.split('\n');
|
|
23
|
+
const sections = [];
|
|
24
|
+
const errors = [];
|
|
25
|
+
let currentSection = null;
|
|
26
|
+
let currentBody = [];
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i];
|
|
29
|
+
const lineNum = i + 1;
|
|
30
|
+
const headerMatch = line.match(SECTION_HEADER_PATTERN);
|
|
31
|
+
if (headerMatch) {
|
|
32
|
+
// Save previous section
|
|
33
|
+
if (currentSection) {
|
|
34
|
+
currentSection.body = currentBody.join('\n');
|
|
35
|
+
const { warnings } = parseFields(currentSection, file);
|
|
36
|
+
errors.push(...warnings);
|
|
37
|
+
sections.push(currentSection);
|
|
38
|
+
}
|
|
39
|
+
const rawType = headerMatch[1].trim();
|
|
40
|
+
const normalizedType = rawType.toLowerCase();
|
|
41
|
+
const title = headerMatch[2].trim();
|
|
42
|
+
if (!validTypes.has(normalizedType)) {
|
|
43
|
+
errors.push({
|
|
44
|
+
file,
|
|
45
|
+
line: lineNum,
|
|
46
|
+
message: `Unknown section type: ${rawType}`,
|
|
47
|
+
suggestion: `Valid types: ${[...validTypes].join(', ')}`,
|
|
48
|
+
severity: 'error',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
currentSection = {
|
|
52
|
+
type: normalizedType.replaceAll(' ', '-'),
|
|
53
|
+
title,
|
|
54
|
+
rawType,
|
|
55
|
+
fields: {},
|
|
56
|
+
body: '',
|
|
57
|
+
line: lineNum,
|
|
58
|
+
};
|
|
59
|
+
currentBody = [];
|
|
60
|
+
}
|
|
61
|
+
else if (currentSection) {
|
|
62
|
+
currentBody.push(line);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Don't forget last section
|
|
66
|
+
if (currentSection) {
|
|
67
|
+
currentSection.body = currentBody.join('\n');
|
|
68
|
+
const { warnings } = parseFields(currentSection, file);
|
|
69
|
+
errors.push(...warnings);
|
|
70
|
+
sections.push(currentSection);
|
|
71
|
+
}
|
|
72
|
+
return { sections, errors };
|
|
73
|
+
}
|
|
74
|
+
const FIELD_PATTERN = /^(\w+)::\s*(.*)$/;
|
|
75
|
+
function parseFields(section, file) {
|
|
76
|
+
const lines = section.body.split('\n');
|
|
77
|
+
const warnings = [];
|
|
78
|
+
const seenFields = new Set();
|
|
79
|
+
let currentField = null;
|
|
80
|
+
let currentValue = [];
|
|
81
|
+
for (let i = 0; i < lines.length; i++) {
|
|
82
|
+
const line = lines[i];
|
|
83
|
+
const lineNum = section.line + i + 1; // +1 because body starts after header
|
|
84
|
+
// Check for sub-header first - starts a new scope for field tracking
|
|
85
|
+
if (line.match(/^#{1,6}\s/)) {
|
|
86
|
+
// Save current field if any
|
|
87
|
+
if (currentField) {
|
|
88
|
+
section.fields[currentField] = currentValue.join('\n').trim();
|
|
89
|
+
currentField = null;
|
|
90
|
+
currentValue = [];
|
|
91
|
+
}
|
|
92
|
+
// Reset seenFields for the new sub-section scope
|
|
93
|
+
seenFields.clear();
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const match = line.match(FIELD_PATTERN);
|
|
97
|
+
if (match) {
|
|
98
|
+
// Save previous field if any
|
|
99
|
+
if (currentField) {
|
|
100
|
+
section.fields[currentField] = currentValue.join('\n').trim();
|
|
101
|
+
}
|
|
102
|
+
currentField = match[1];
|
|
103
|
+
const inlineValue = match[2].trim();
|
|
104
|
+
currentValue = inlineValue ? [inlineValue] : [];
|
|
105
|
+
// Check for duplicate field
|
|
106
|
+
if (seenFields.has(currentField)) {
|
|
107
|
+
warnings.push({
|
|
108
|
+
file,
|
|
109
|
+
line: lineNum,
|
|
110
|
+
message: `Duplicate field '${currentField}' (previous value will be overwritten)`,
|
|
111
|
+
suggestion: `Remove the duplicate '${currentField}::' definition`,
|
|
112
|
+
severity: 'warning',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
seenFields.add(currentField);
|
|
116
|
+
}
|
|
117
|
+
else if (currentField) {
|
|
118
|
+
// Continue multiline value
|
|
119
|
+
currentValue.push(line);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Save final field
|
|
123
|
+
if (currentField) {
|
|
124
|
+
section.fields[currentField] = currentValue.join('\n').trim();
|
|
125
|
+
}
|
|
126
|
+
return { warnings };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/parser/sections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// src/parser/sections.test.ts
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { parseSections, MODULE_SECTION_TYPES, LENS_SECTION_TYPES, LO_SECTION_TYPES } from './sections.js';
|
|
4
|
+
describe('parseSections', () => {
|
|
5
|
+
it('splits content by H1 headers for modules', () => {
|
|
6
|
+
const content = `
|
|
7
|
+
# Learning Outcome: First Section
|
|
8
|
+
source:: [[../Learning Outcomes/lo1.md|LO1]]
|
|
9
|
+
|
|
10
|
+
# Page: Second Section
|
|
11
|
+
id:: 123
|
|
12
|
+
|
|
13
|
+
Some content here.
|
|
14
|
+
`;
|
|
15
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES);
|
|
16
|
+
expect(result.sections).toHaveLength(2);
|
|
17
|
+
expect(result.sections[0].type).toBe('learning-outcome');
|
|
18
|
+
expect(result.sections[0].title).toBe('First Section');
|
|
19
|
+
expect(result.sections[1].type).toBe('page');
|
|
20
|
+
expect(result.sections[1].title).toBe('Second Section');
|
|
21
|
+
});
|
|
22
|
+
it('splits content by H3 headers for lens files', () => {
|
|
23
|
+
const content = `
|
|
24
|
+
### Text: Introduction
|
|
25
|
+
|
|
26
|
+
#### Text
|
|
27
|
+
content:: Hello world.
|
|
28
|
+
|
|
29
|
+
### Article: Deep Dive
|
|
30
|
+
source:: [[../articles/deep.md|Article]]
|
|
31
|
+
`;
|
|
32
|
+
const result = parseSections(content, 3, LENS_SECTION_TYPES);
|
|
33
|
+
expect(result.sections).toHaveLength(2);
|
|
34
|
+
expect(result.sections[0].type).toBe('text');
|
|
35
|
+
expect(result.sections[1].type).toBe('article');
|
|
36
|
+
});
|
|
37
|
+
it('extracts fields from section body', () => {
|
|
38
|
+
const content = `
|
|
39
|
+
# Learning Outcome: Test
|
|
40
|
+
source:: [[../Learning Outcomes/lo1.md|LO1]]
|
|
41
|
+
optional:: true
|
|
42
|
+
`;
|
|
43
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES);
|
|
44
|
+
expect(result.sections[0].fields.source).toBe('[[../Learning Outcomes/lo1.md|LO1]]');
|
|
45
|
+
expect(result.sections[0].fields.optional).toBe('true');
|
|
46
|
+
});
|
|
47
|
+
it('returns error for unknown section type', () => {
|
|
48
|
+
const content = `
|
|
49
|
+
# Unknown: Bad Section
|
|
50
|
+
content:: here
|
|
51
|
+
`;
|
|
52
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES);
|
|
53
|
+
expect(result.errors).toHaveLength(1);
|
|
54
|
+
expect(result.errors[0].message).toContain('Unknown section type');
|
|
55
|
+
});
|
|
56
|
+
describe('multiline fields', () => {
|
|
57
|
+
it('parses content:: spanning multiple lines', () => {
|
|
58
|
+
const content = `
|
|
59
|
+
### Text: Intro
|
|
60
|
+
|
|
61
|
+
#### Text
|
|
62
|
+
content::
|
|
63
|
+
This is line one.
|
|
64
|
+
This is line two.
|
|
65
|
+
This is line three.
|
|
66
|
+
|
|
67
|
+
#### Chat
|
|
68
|
+
instructions:: Next segment
|
|
69
|
+
`;
|
|
70
|
+
const result = parseSections(content, 3, LENS_SECTION_TYPES);
|
|
71
|
+
// The section body should contain the multiline content
|
|
72
|
+
expect(result.sections[0].body).toContain('content::\nThis is line one.');
|
|
73
|
+
});
|
|
74
|
+
it('parses field with value on next line after ::', () => {
|
|
75
|
+
const content = `
|
|
76
|
+
## Lens:
|
|
77
|
+
source::
|
|
78
|
+
![[../Lenses/test]]
|
|
79
|
+
`;
|
|
80
|
+
const result = parseSections(content, 2, LO_SECTION_TYPES);
|
|
81
|
+
expect(result.sections[0].fields.source).toBe('![[../Lenses/test]]');
|
|
82
|
+
});
|
|
83
|
+
it('parses multiline field that continues until next field', () => {
|
|
84
|
+
const content = `
|
|
85
|
+
## Lens: Test
|
|
86
|
+
instructions::
|
|
87
|
+
First line.
|
|
88
|
+
Second line.
|
|
89
|
+
Third line.
|
|
90
|
+
source:: [[test.md|Test]]
|
|
91
|
+
`;
|
|
92
|
+
const result = parseSections(content, 2, LO_SECTION_TYPES);
|
|
93
|
+
expect(result.sections[0].fields.instructions).toBe('First line.\nSecond line.\nThird line.');
|
|
94
|
+
expect(result.sections[0].fields.source).toBe('[[test.md|Test]]');
|
|
95
|
+
});
|
|
96
|
+
it('parses multiline field that continues until end of section', () => {
|
|
97
|
+
const content = `
|
|
98
|
+
### Text: Notes
|
|
99
|
+
|
|
100
|
+
#### Text
|
|
101
|
+
content::
|
|
102
|
+
Line one.
|
|
103
|
+
Line two.
|
|
104
|
+
Line three.
|
|
105
|
+
`;
|
|
106
|
+
const result = parseSections(content, 3, LENS_SECTION_TYPES);
|
|
107
|
+
// The parseFields function should extract multiline content
|
|
108
|
+
// Note: body contains subsections too, we need to test fields parsing
|
|
109
|
+
expect(result.sections[0].body).toContain('content::\nLine one.');
|
|
110
|
+
});
|
|
111
|
+
it('handles field with empty value followed by content on next line', () => {
|
|
112
|
+
const content = `
|
|
113
|
+
# Page: Welcome
|
|
114
|
+
id:: abc-123
|
|
115
|
+
content::
|
|
116
|
+
Welcome to the course.
|
|
117
|
+
This is the intro text.
|
|
118
|
+
`;
|
|
119
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES);
|
|
120
|
+
expect(result.sections[0].fields.id).toBe('abc-123');
|
|
121
|
+
expect(result.sections[0].fields.content).toBe('Welcome to the course.\nThis is the intro text.');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('empty section titles', () => {
|
|
125
|
+
it('handles empty section title (colon with no title after)', () => {
|
|
126
|
+
const content = `
|
|
127
|
+
## Lens:
|
|
128
|
+
source:: [[../Lenses/test.md|Test]]
|
|
129
|
+
|
|
130
|
+
## Lens:
|
|
131
|
+
source:: [[../Lenses/other.md|Other]]
|
|
132
|
+
`;
|
|
133
|
+
const result = parseSections(content, 2, LO_SECTION_TYPES);
|
|
134
|
+
expect(result.sections).toHaveLength(2);
|
|
135
|
+
expect(result.sections[0].title).toBe('');
|
|
136
|
+
expect(result.sections[0].type).toBe('lens');
|
|
137
|
+
expect(result.sections[1].title).toBe('');
|
|
138
|
+
});
|
|
139
|
+
it('handles empty section title with trailing whitespace', () => {
|
|
140
|
+
const content = `
|
|
141
|
+
## Lens:
|
|
142
|
+
source:: [[../Lenses/test.md|Test]]
|
|
143
|
+
`;
|
|
144
|
+
const result = parseSections(content, 2, LO_SECTION_TYPES);
|
|
145
|
+
expect(result.sections).toHaveLength(1);
|
|
146
|
+
expect(result.sections[0].title).toBe('');
|
|
147
|
+
expect(result.sections[0].type).toBe('lens');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
describe('duplicate field warnings', () => {
|
|
151
|
+
it('warns about duplicate field definitions', () => {
|
|
152
|
+
const content = `
|
|
153
|
+
# Page: Test
|
|
154
|
+
content:: First value
|
|
155
|
+
content:: Second value
|
|
156
|
+
`;
|
|
157
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
158
|
+
expect(result.errors.some(e => e.severity === 'warning' &&
|
|
159
|
+
e.message.includes('Duplicate'))).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
it('uses the last value when field is duplicated', () => {
|
|
162
|
+
const content = `
|
|
163
|
+
# Page: Test
|
|
164
|
+
id:: first-id
|
|
165
|
+
id:: second-id
|
|
166
|
+
`;
|
|
167
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
168
|
+
// The last value should win
|
|
169
|
+
expect(result.sections[0].fields.id).toBe('second-id');
|
|
170
|
+
});
|
|
171
|
+
it('does not warn when different fields are defined', () => {
|
|
172
|
+
const content = `
|
|
173
|
+
# Page: Test
|
|
174
|
+
id:: some-id
|
|
175
|
+
content:: Some content
|
|
176
|
+
`;
|
|
177
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
178
|
+
// No warnings about duplicates
|
|
179
|
+
expect(result.errors.filter(e => e.severity === 'warning' &&
|
|
180
|
+
e.message.includes('Duplicate'))).toHaveLength(0);
|
|
181
|
+
});
|
|
182
|
+
it('does not warn about duplicate fields when they are in different sub-sections', () => {
|
|
183
|
+
// This is the real-world case: a Lens file with multiple Text segments,
|
|
184
|
+
// each with their own content:: field. These are NOT duplicates.
|
|
185
|
+
const content = `
|
|
186
|
+
### Article: Cascades and Cycles
|
|
187
|
+
source:: [[../articles/cascades.md]]
|
|
188
|
+
|
|
189
|
+
#### Text
|
|
190
|
+
content::
|
|
191
|
+
Introduction paragraph explaining cybernetics and feedback loops.
|
|
192
|
+
|
|
193
|
+
#### Article-excerpt
|
|
194
|
+
from:: "Cascades are when"
|
|
195
|
+
to:: "neutron multiplication factor"
|
|
196
|
+
|
|
197
|
+
#### Text
|
|
198
|
+
content::
|
|
199
|
+
What are the properties that make something a cycle rather than a cascade?
|
|
200
|
+
|
|
201
|
+
#### Chat: Discussion
|
|
202
|
+
instructions::
|
|
203
|
+
TLDR of what the user just read about cascades and cycles.
|
|
204
|
+
Discuss the difference between cascades and cycles.
|
|
205
|
+
|
|
206
|
+
### Video: Intelligence Explosion
|
|
207
|
+
source:: [[../video_transcripts/intelligence.md]]
|
|
208
|
+
|
|
209
|
+
#### Text
|
|
210
|
+
content::
|
|
211
|
+
Watch this video about intelligence feedback loops.
|
|
212
|
+
|
|
213
|
+
#### Video-excerpt
|
|
214
|
+
from:: 0:00
|
|
215
|
+
to:: 14:49
|
|
216
|
+
|
|
217
|
+
#### Text
|
|
218
|
+
content::
|
|
219
|
+
What surprised you about the video?
|
|
220
|
+
|
|
221
|
+
#### Chat: Video Discussion
|
|
222
|
+
instructions::
|
|
223
|
+
Discuss what the user learned from the video.
|
|
224
|
+
`;
|
|
225
|
+
const result = parseSections(content, 3, LENS_SECTION_TYPES, 'test-lens.md');
|
|
226
|
+
// Should have 2 sections (Article and Video)
|
|
227
|
+
expect(result.sections).toHaveLength(2);
|
|
228
|
+
// Should NOT have any duplicate field warnings
|
|
229
|
+
const duplicateWarnings = result.errors.filter(e => e.severity === 'warning' &&
|
|
230
|
+
e.message.includes('Duplicate'));
|
|
231
|
+
expect(duplicateWarnings).toHaveLength(0);
|
|
232
|
+
// Verify the sections were parsed correctly
|
|
233
|
+
expect(result.sections[0].type).toBe('article');
|
|
234
|
+
expect(result.sections[0].title).toBe('Cascades and Cycles');
|
|
235
|
+
expect(result.sections[0].fields.source).toBe('[[../articles/cascades.md]]');
|
|
236
|
+
expect(result.sections[1].type).toBe('video');
|
|
237
|
+
expect(result.sections[1].title).toBe('Intelligence Explosion');
|
|
238
|
+
expect(result.sections[1].fields.source).toBe('[[../video_transcripts/intelligence.md]]');
|
|
239
|
+
});
|
|
240
|
+
it('still warns about actual duplicates within the same sub-section', () => {
|
|
241
|
+
const content = `
|
|
242
|
+
### Text: Intro
|
|
243
|
+
|
|
244
|
+
#### Text
|
|
245
|
+
content:: First paragraph
|
|
246
|
+
content:: Second paragraph that overwrites the first
|
|
247
|
+
`;
|
|
248
|
+
const result = parseSections(content, 3, LENS_SECTION_TYPES, 'test.md');
|
|
249
|
+
// This IS a real duplicate - same field twice within the same #### Text segment
|
|
250
|
+
const duplicateWarnings = result.errors.filter(e => e.severity === 'warning' &&
|
|
251
|
+
e.message.includes('Duplicate'));
|
|
252
|
+
expect(duplicateWarnings).toHaveLength(1);
|
|
253
|
+
expect(duplicateWarnings[0].message).toContain("content");
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe('special characters in titles', () => {
|
|
257
|
+
it('handles ampersand in section title', () => {
|
|
258
|
+
const content = `
|
|
259
|
+
# Page: Safety & Alignment
|
|
260
|
+
content:: This section covers both topics.
|
|
261
|
+
`;
|
|
262
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
263
|
+
expect(result.sections).toHaveLength(1);
|
|
264
|
+
expect(result.sections[0].title).toBe('Safety & Alignment');
|
|
265
|
+
expect(result.sections[0].type).toBe('page');
|
|
266
|
+
expect(result.errors).toHaveLength(0);
|
|
267
|
+
});
|
|
268
|
+
it('handles apostrophe in section title', () => {
|
|
269
|
+
const content = `
|
|
270
|
+
# Page: What's Next
|
|
271
|
+
content:: Looking ahead.
|
|
272
|
+
`;
|
|
273
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
274
|
+
expect(result.sections).toHaveLength(1);
|
|
275
|
+
expect(result.sections[0].title).toBe("What's Next");
|
|
276
|
+
});
|
|
277
|
+
it('handles colon in section title', () => {
|
|
278
|
+
const content = `
|
|
279
|
+
# Page: Part 1: Introduction
|
|
280
|
+
content:: The beginning.
|
|
281
|
+
`;
|
|
282
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
283
|
+
expect(result.sections).toHaveLength(1);
|
|
284
|
+
expect(result.sections[0].title).toBe('Part 1: Introduction');
|
|
285
|
+
});
|
|
286
|
+
it('handles multiple special characters in section title', () => {
|
|
287
|
+
const content = `
|
|
288
|
+
# Page: AI Safety & Alignment: What's at Stake?
|
|
289
|
+
content:: Important questions.
|
|
290
|
+
`;
|
|
291
|
+
const result = parseSections(content, 1, MODULE_SECTION_TYPES, 'test.md');
|
|
292
|
+
expect(result.sections).toHaveLength(1);
|
|
293
|
+
expect(result.sections[0].title).toBe("AI Safety & Alignment: What's at Stake?");
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
//# sourceMappingURL=sections.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.test.js","sourceRoot":"","sources":["../../src/parser/sections.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface WikilinkParts {
|
|
2
|
+
path: string;
|
|
3
|
+
display?: string;
|
|
4
|
+
isEmbed?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseWikilink(text: string): WikilinkParts | null;
|
|
7
|
+
export declare function resolveWikilinkPath(linkPath: string, sourceFile: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Find a file in the files Map, trying with and without .md extension.
|
|
10
|
+
* Returns the key that exists in the map, or null if not found.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findFileWithExtension(path: string, files: Map<string, string>): string | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/parser/wikilink.ts
|
|
2
|
+
import { join, dirname, normalize } from 'path';
|
|
3
|
+
// Matches [[path]], [[path|display]], ![[embed]], ![[embed|display]]
|
|
4
|
+
const WIKILINK_PATTERN = /^!?\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/;
|
|
5
|
+
export function parseWikilink(text) {
|
|
6
|
+
const match = text.match(WIKILINK_PATTERN);
|
|
7
|
+
if (!match)
|
|
8
|
+
return null;
|
|
9
|
+
return {
|
|
10
|
+
path: match[1].trim(),
|
|
11
|
+
display: match[2]?.trim(),
|
|
12
|
+
isEmbed: text.startsWith('!'),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function resolveWikilinkPath(linkPath, sourceFile) {
|
|
16
|
+
// Use Node's path module - normalize handles .. and . segments
|
|
17
|
+
return normalize(join(dirname(sourceFile), linkPath)).replace(/\\/g, '/');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Find a file in the files Map, trying with and without .md extension.
|
|
21
|
+
* Returns the key that exists in the map, or null if not found.
|
|
22
|
+
*/
|
|
23
|
+
export function findFileWithExtension(path, files) {
|
|
24
|
+
// Try exact match first
|
|
25
|
+
if (files.has(path)) {
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
// Try adding .md extension
|
|
29
|
+
const withMd = path + '.md';
|
|
30
|
+
if (files.has(withMd)) {
|
|
31
|
+
return withMd;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=wikilink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wikilink.js","sourceRoot":"","sources":["../../src/parser/wikilink.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAQhD,qEAAqE;AACrE,MAAM,gBAAgB,GAAG,sCAAsC,CAAC;AAEhE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;QACrB,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;QACzB,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,UAAkB;IACtE,+DAA+D;IAC/D,OAAO,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC5E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,KAA0B;IAC5E,wBAAwB;IACxB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2BAA2B;IAC3B,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;IAC5B,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|