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.
Files changed (103) hide show
  1. package/dist/bundler/article.d.ts +41 -0
  2. package/dist/bundler/article.js +240 -0
  3. package/dist/bundler/article.js.map +1 -0
  4. package/dist/bundler/article.test.d.ts +1 -0
  5. package/dist/bundler/article.test.js +212 -0
  6. package/dist/bundler/article.test.js.map +1 -0
  7. package/dist/bundler/video.d.ts +50 -0
  8. package/dist/bundler/video.js +213 -0
  9. package/dist/bundler/video.js.map +1 -0
  10. package/dist/bundler/video.test.d.ts +1 -0
  11. package/dist/bundler/video.test.js +95 -0
  12. package/dist/bundler/video.test.js.map +1 -0
  13. package/dist/cli.d.ts +10 -0
  14. package/dist/cli.js +80 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/cli.test.d.ts +1 -0
  17. package/dist/cli.test.js +45 -0
  18. package/dist/cli.test.js.map +1 -0
  19. package/dist/flattener/index.d.ts +20 -0
  20. package/dist/flattener/index.js +707 -0
  21. package/dist/flattener/index.js.map +1 -0
  22. package/dist/flattener/index.test.d.ts +1 -0
  23. package/dist/flattener/index.test.js +547 -0
  24. package/dist/flattener/index.test.js.map +1 -0
  25. package/dist/fs/read-vault.d.ts +4 -0
  26. package/dist/fs/read-vault.js +28 -0
  27. package/dist/fs/read-vault.js.map +1 -0
  28. package/dist/fs/read-vault.test.d.ts +1 -0
  29. package/dist/fs/read-vault.test.js +37 -0
  30. package/dist/fs/read-vault.test.js.map +1 -0
  31. package/dist/index.d.ts +75 -0
  32. package/dist/index.js +137 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/parser/course.d.ts +17 -0
  35. package/dist/parser/course.js +145 -0
  36. package/dist/parser/course.js.map +1 -0
  37. package/dist/parser/course.test.d.ts +1 -0
  38. package/dist/parser/course.test.js +117 -0
  39. package/dist/parser/course.test.js.map +1 -0
  40. package/dist/parser/frontmatter.d.ts +8 -0
  41. package/dist/parser/frontmatter.js +58 -0
  42. package/dist/parser/frontmatter.js.map +1 -0
  43. package/dist/parser/frontmatter.test.d.ts +1 -0
  44. package/dist/parser/frontmatter.test.js +54 -0
  45. package/dist/parser/frontmatter.test.js.map +1 -0
  46. package/dist/parser/learning-outcome.d.ts +21 -0
  47. package/dist/parser/learning-outcome.js +116 -0
  48. package/dist/parser/learning-outcome.js.map +1 -0
  49. package/dist/parser/learning-outcome.test.d.ts +1 -0
  50. package/dist/parser/learning-outcome.test.js +75 -0
  51. package/dist/parser/learning-outcome.test.js.map +1 -0
  52. package/dist/parser/lens.d.ts +51 -0
  53. package/dist/parser/lens.js +328 -0
  54. package/dist/parser/lens.js.map +1 -0
  55. package/dist/parser/lens.test.d.ts +1 -0
  56. package/dist/parser/lens.test.js +453 -0
  57. package/dist/parser/lens.test.js.map +1 -0
  58. package/dist/parser/module.d.ts +21 -0
  59. package/dist/parser/module.js +128 -0
  60. package/dist/parser/module.js.map +1 -0
  61. package/dist/parser/module.test.d.ts +1 -0
  62. package/dist/parser/module.test.js +99 -0
  63. package/dist/parser/module.test.js.map +1 -0
  64. package/dist/parser/sections.d.ts +18 -0
  65. package/dist/parser/sections.js +128 -0
  66. package/dist/parser/sections.js.map +1 -0
  67. package/dist/parser/sections.test.d.ts +1 -0
  68. package/dist/parser/sections.test.js +297 -0
  69. package/dist/parser/sections.test.js.map +1 -0
  70. package/dist/parser/wikilink.d.ts +12 -0
  71. package/dist/parser/wikilink.js +35 -0
  72. package/dist/parser/wikilink.js.map +1 -0
  73. package/dist/parser/wikilink.test.d.ts +1 -0
  74. package/dist/parser/wikilink.test.js +76 -0
  75. package/dist/parser/wikilink.test.js.map +1 -0
  76. package/dist/validator/field-typos.d.ts +10 -0
  77. package/dist/validator/field-typos.js +96 -0
  78. package/dist/validator/field-typos.js.map +1 -0
  79. package/dist/validator/field-typos.test.d.ts +1 -0
  80. package/dist/validator/field-typos.test.js +41 -0
  81. package/dist/validator/field-typos.test.js.map +1 -0
  82. package/dist/validator/field-values.d.ts +11 -0
  83. package/dist/validator/field-values.js +37 -0
  84. package/dist/validator/field-values.js.map +1 -0
  85. package/dist/validator/field-values.test.d.ts +1 -0
  86. package/dist/validator/field-values.test.js +45 -0
  87. package/dist/validator/field-values.test.js.map +1 -0
  88. package/dist/validator/segment-fields.d.ts +12 -0
  89. package/dist/validator/segment-fields.js +55 -0
  90. package/dist/validator/segment-fields.js.map +1 -0
  91. package/dist/validator/segment-fields.test.d.ts +1 -0
  92. package/dist/validator/segment-fields.test.js +73 -0
  93. package/dist/validator/segment-fields.test.js.map +1 -0
  94. package/dist/validator/standalone.test.d.ts +1 -0
  95. package/dist/validator/standalone.test.js +190 -0
  96. package/dist/validator/standalone.test.js.map +1 -0
  97. package/dist/validator/uuid.d.ts +17 -0
  98. package/dist/validator/uuid.js +46 -0
  99. package/dist/validator/uuid.js.map +1 -0
  100. package/dist/validator/uuid.test.d.ts +1 -0
  101. package/dist/validator/uuid.test.js +229 -0
  102. package/dist/validator/uuid.test.js.map +1 -0
  103. 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":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEjE,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,OAAO,GAAG;;;;;;;CAOnB,CAAC;QAEE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;QAExD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,OAAO,GAAG;;;CAGnB,CAAC;QAEE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAEtD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,OAAO,GAAG;;;;;;;;;;;;CAYnB,CAAC;QAEE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QAEvD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QAC1F,iDAAiD;QACjD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC7D,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,IAAI,GAAG;;;;;;CAMhB,CAAC;QAEE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAE7C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACtD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,IAAI,GAAG;;;;;;;;;CAShB,CAAC;QAEE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAE7C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,IAAI,GAAG;;CAEhB,CAAC;QAEE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAE7C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,IAAI,GAAG;;CAEhB,CAAC;QAEE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAE7C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -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 {};