@unrdf/kgn 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/package.json +90 -0
  4. package/src/MIGRATION_COMPLETE.md +186 -0
  5. package/src/PORT-MAP.md +302 -0
  6. package/src/base/filter-templates.js +479 -0
  7. package/src/base/index.js +92 -0
  8. package/src/base/injection-targets.js +583 -0
  9. package/src/base/macro-templates.js +298 -0
  10. package/src/base/macro-templates.js.bak +461 -0
  11. package/src/base/shacl-templates.js +617 -0
  12. package/src/base/template-base.js +388 -0
  13. package/src/core/attestor.js +381 -0
  14. package/src/core/filters.js +518 -0
  15. package/src/core/index.js +21 -0
  16. package/src/core/kgen-engine.js +372 -0
  17. package/src/core/parser.js +447 -0
  18. package/src/core/post-processor.js +313 -0
  19. package/src/core/renderer.js +469 -0
  20. package/src/doc-generator/cli.mjs +122 -0
  21. package/src/doc-generator/index.mjs +28 -0
  22. package/src/doc-generator/mdx-generator.mjs +71 -0
  23. package/src/doc-generator/nav-generator.mjs +136 -0
  24. package/src/doc-generator/parser.mjs +291 -0
  25. package/src/doc-generator/rdf-builder.mjs +306 -0
  26. package/src/doc-generator/scanner.mjs +189 -0
  27. package/src/engine/index.js +42 -0
  28. package/src/engine/pipeline.js +448 -0
  29. package/src/engine/renderer.js +604 -0
  30. package/src/engine/template-engine.js +566 -0
  31. package/src/filters/array.js +436 -0
  32. package/src/filters/data.js +479 -0
  33. package/src/filters/index.js +270 -0
  34. package/src/filters/rdf.js +264 -0
  35. package/src/filters/text.js +369 -0
  36. package/src/index.js +109 -0
  37. package/src/inheritance/index.js +40 -0
  38. package/src/injection/api.js +260 -0
  39. package/src/injection/atomic-writer.js +327 -0
  40. package/src/injection/constants.js +136 -0
  41. package/src/injection/idempotency-manager.js +295 -0
  42. package/src/injection/index.js +28 -0
  43. package/src/injection/injection-engine.js +378 -0
  44. package/src/injection/integration.js +339 -0
  45. package/src/injection/modes/index.js +341 -0
  46. package/src/injection/rollback-manager.js +373 -0
  47. package/src/injection/target-resolver.js +323 -0
  48. package/src/injection/tests/atomic-writer.test.js +382 -0
  49. package/src/injection/tests/injection-engine.test.js +611 -0
  50. package/src/injection/tests/integration.test.js +392 -0
  51. package/src/injection/tests/run-tests.js +283 -0
  52. package/src/injection/validation-engine.js +547 -0
  53. package/src/linter/determinism-linter.js +473 -0
  54. package/src/linter/determinism.js +410 -0
  55. package/src/linter/index.js +6 -0
  56. package/src/linter/test-doubles.js +475 -0
  57. package/src/parser/frontmatter.js +228 -0
  58. package/src/parser/variables.js +344 -0
  59. package/src/renderer/deterministic.js +245 -0
  60. package/src/renderer/index.js +6 -0
  61. package/src/templates/latex/academic-paper.njk +186 -0
  62. package/src/templates/latex/index.js +104 -0
  63. package/src/templates/nextjs/app-page.njk +66 -0
  64. package/src/templates/nextjs/index.js +80 -0
  65. package/src/templates/office/docx/document.njk +368 -0
  66. package/src/templates/office/index.js +79 -0
  67. package/src/templates/office/word-report.njk +129 -0
  68. package/src/utils/template-utils.js +426 -0
@@ -0,0 +1,447 @@
1
+ /**
2
+ * KGEN Template Parser - Parse template syntax without nunjucks
3
+ *
4
+ * Supports:
5
+ * - Variables: {{ variable }}
6
+ * - Filters: {{ variable | filter }}
7
+ * - Conditions: {% if condition %}...{% endif %}
8
+ * - Loops: {% for item in items %}...{% endfor %}
9
+ * - Comments: {# comment #}
10
+ * - Frontmatter: YAML header
11
+ */
12
+
13
+ // Use existing frontmatter parser if available, fallback to basic parsing
14
+
15
+ export class KGenParser {
16
+ constructor(options = {}) {
17
+ this.options = {
18
+ maxDepth: options.maxDepth || 10,
19
+ enableIncludes: options.enableIncludes !== false,
20
+ strictMode: options.strictMode !== false,
21
+ ...options
22
+ };
23
+
24
+ // Template syntax patterns
25
+ this.patterns = {
26
+ variable: /\{\{\s*([^}]+)\s*\}\}/g,
27
+ expression: /\{\%\s*([^%]+)\s*\%\}/g,
28
+ comment: /\{#\s*([^#]+)\s*#\}/g,
29
+ frontmatter: /^---\n([\s\S]*?)\n---\n([\s\S]*)$/
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Parse template content into structured format
35
+ */
36
+ async parse(template) {
37
+ try {
38
+ // Extract frontmatter if present
39
+ const { frontmatter, content } = this.parseFrontmatter(template);
40
+
41
+ // Parse template structure
42
+ const parseResult = {
43
+ template: content,
44
+ frontmatter: frontmatter || {},
45
+ variables: this.extractVariables(content),
46
+ expressions: this.extractExpressions(content),
47
+ includes: this.extractIncludes(content),
48
+ comments: this.extractComments(content),
49
+ structure: this.analyzeStructure(content)
50
+ };
51
+
52
+ // Validate syntax
53
+ if (this.options.strictMode) {
54
+ this.validateSyntax(parseResult);
55
+ }
56
+
57
+ return parseResult;
58
+ } catch (error) {
59
+ throw new Error(`Parse error: ${error.message}`);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Parse frontmatter from template
65
+ */
66
+ parseFrontmatter(template) {
67
+ const match = template.match(this.patterns.frontmatter);
68
+
69
+ if (match) {
70
+ try {
71
+ // Try to parse YAML frontmatter
72
+ const yamlContent = match[1];
73
+ const templateContent = match[2];
74
+
75
+ // Basic YAML parsing (simplified)
76
+ const frontmatter = this.parseBasicYAML(yamlContent);
77
+
78
+ return {
79
+ frontmatter,
80
+ content: templateContent
81
+ };
82
+ } catch (error) {
83
+ // Fallback to basic parsing
84
+ return {
85
+ frontmatter: {},
86
+ content: template
87
+ };
88
+ }
89
+ }
90
+
91
+ return {
92
+ frontmatter: {},
93
+ content: template
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Basic YAML parser for frontmatter (simplified)
99
+ */
100
+ parseBasicYAML(yamlContent) {
101
+ const result = {};
102
+ const lines = yamlContent.split('\n');
103
+
104
+ for (const line of lines) {
105
+ const trimmed = line.trim();
106
+ if (!trimmed || trimmed.startsWith('#')) continue;
107
+
108
+ const colonIndex = trimmed.indexOf(':');
109
+ if (colonIndex === -1) continue;
110
+
111
+ const key = trimmed.substring(0, colonIndex).trim();
112
+ let value = trimmed.substring(colonIndex + 1).trim();
113
+
114
+ // Parse basic value types
115
+ if (value.startsWith('"') && value.endsWith('"')) {
116
+ value = value.slice(1, -1);
117
+ } else if (value.startsWith("'") && value.endsWith("'")) {
118
+ value = value.slice(1, -1);
119
+ } else if (value === 'true') {
120
+ value = true;
121
+ } else if (value === 'false') {
122
+ value = false;
123
+ } else if (value === 'null') {
124
+ value = null;
125
+ } else if (/^-?\d+(\.\d+)?$/.test(value)) {
126
+ value = parseFloat(value);
127
+ }
128
+
129
+ result[key] = value;
130
+ }
131
+
132
+ return result;
133
+ }
134
+
135
+ /**
136
+ * Extract variables from template content
137
+ */
138
+ extractVariables(content) {
139
+ const variables = new Set();
140
+ const optionalVariables = new Set();
141
+ let match;
142
+
143
+ // Reset regex state
144
+ this.patterns.variable.lastIndex = 0;
145
+
146
+ while ((match = this.patterns.variable.exec(content)) !== null) {
147
+ const variableExpr = match[1].trim();
148
+
149
+ // Handle filtered variables: {{ variable | filter }}
150
+ const parts = variableExpr.split('|');
151
+ const [variableName] = parts[0].trim().split('.');
152
+
153
+ // Skip loop variables and built-ins
154
+ if (!this.isBuiltinVariable(variableName)) {
155
+ variables.add(variableName);
156
+
157
+ // Check if variable has default filter (makes it optional)
158
+ if (parts.length > 1) {
159
+ const filters = parts.slice(1);
160
+ for (const filterExpr of filters) {
161
+ const filterName = filterExpr.trim().split(/[\s(]/)[0];
162
+ if (filterName === 'default') {
163
+ optionalVariables.add(variableName);
164
+ break;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Return only required variables (excluding optional ones)
172
+ const required = Array.from(variables).filter(v => !optionalVariables.has(v));
173
+ return required;
174
+ }
175
+
176
+ /**
177
+ * Extract expressions (conditionals, loops, etc.)
178
+ */
179
+ extractExpressions(content) {
180
+ const expressions = [];
181
+ let match;
182
+
183
+ // Reset regex state
184
+ this.patterns.expression.lastIndex = 0;
185
+
186
+ while ((match = this.patterns.expression.exec(content)) !== null) {
187
+ const expr = match[1].trim();
188
+ const type = this.getExpressionType(expr);
189
+
190
+ expressions.push({
191
+ type,
192
+ expression: expr,
193
+ position: match.index,
194
+ raw: match[0]
195
+ });
196
+ }
197
+
198
+ return expressions;
199
+ }
200
+
201
+ /**
202
+ * Extract include statements
203
+ */
204
+ extractIncludes(content) {
205
+ const includes = [];
206
+ const expressions = this.extractExpressions(content);
207
+
208
+ expressions.forEach(expr => {
209
+ if (expr.type === 'include') {
210
+ const pathMatch = expr.expression.match(/include\s+['"]([^'"]+)['"]/);
211
+ if (pathMatch) {
212
+ includes.push({
213
+ path: pathMatch[1],
214
+ position: expr.position,
215
+ raw: expr.raw
216
+ });
217
+ }
218
+ }
219
+ });
220
+
221
+ return includes;
222
+ }
223
+
224
+ /**
225
+ * Extract comments
226
+ */
227
+ extractComments(content) {
228
+ const comments = [];
229
+ let match;
230
+
231
+ // Reset regex state
232
+ this.patterns.comment.lastIndex = 0;
233
+
234
+ while ((match = this.patterns.comment.exec(content)) !== null) {
235
+ comments.push({
236
+ text: match[1].trim(),
237
+ position: match.index,
238
+ raw: match[0]
239
+ });
240
+ }
241
+
242
+ return comments;
243
+ }
244
+
245
+ /**
246
+ * Analyze template structure
247
+ */
248
+ analyzeStructure(content) {
249
+ const expressions = this.extractExpressions(content);
250
+
251
+ const structure = {
252
+ conditionals: 0,
253
+ loops: 0,
254
+ includes: 0,
255
+ blocks: 0,
256
+ macros: 0,
257
+ depth: 0
258
+ };
259
+
260
+ const stack = [];
261
+
262
+ expressions.forEach(expr => {
263
+ switch (expr.type) {
264
+ case 'if':
265
+ structure.conditionals++;
266
+ stack.push('if');
267
+ break;
268
+ case 'elif':
269
+ case 'else':
270
+ // Don't increment depth for elif/else
271
+ break;
272
+ case 'endif':
273
+ if (stack.length > 0 && stack[stack.length - 1] === 'if') {
274
+ stack.pop();
275
+ }
276
+ break;
277
+ case 'for':
278
+ structure.loops++;
279
+ stack.push('for');
280
+ break;
281
+ case 'endfor':
282
+ if (stack.length > 0 && stack[stack.length - 1] === 'for') {
283
+ stack.pop();
284
+ }
285
+ break;
286
+ case 'include':
287
+ structure.includes++;
288
+ break;
289
+ case 'block':
290
+ structure.blocks++;
291
+ stack.push('block');
292
+ break;
293
+ case 'endblock':
294
+ if (stack.length > 0 && stack[stack.length - 1] === 'block') {
295
+ stack.pop();
296
+ }
297
+ break;
298
+ case 'macro':
299
+ structure.macros++;
300
+ break;
301
+ }
302
+
303
+ // Track maximum nesting depth
304
+ structure.depth = Math.max(structure.depth, stack.length);
305
+ });
306
+
307
+ return structure;
308
+ }
309
+
310
+ /**
311
+ * Get expression type from expression string
312
+ */
313
+ getExpressionType(expr) {
314
+ const trimmed = expr.trim().toLowerCase();
315
+
316
+ if (trimmed.startsWith('if ')) return 'if';
317
+ if (trimmed === 'else') return 'else';
318
+ if (trimmed.startsWith('elif ')) return 'elif';
319
+ if (trimmed === 'endif') return 'endif';
320
+ if (trimmed.startsWith('for ')) return 'for';
321
+ if (trimmed === 'endfor') return 'endfor';
322
+ if (trimmed.startsWith('include ')) return 'include';
323
+ if (trimmed.startsWith('block ')) return 'block';
324
+ if (trimmed === 'endblock') return 'endblock';
325
+ if (trimmed.startsWith('macro ')) return 'macro';
326
+ if (trimmed === 'endmacro') return 'endmacro';
327
+ if (trimmed.startsWith('set ')) return 'set';
328
+
329
+ return 'unknown';
330
+ }
331
+
332
+ /**
333
+ * Check if variable is a built-in
334
+ */
335
+ isBuiltinVariable(name) {
336
+ const builtins = new Set([
337
+ 'loop', 'super', '__kgen', 'true', 'false', 'null', 'undefined'
338
+ ]);
339
+
340
+ return builtins.has(name) || name.startsWith('__');
341
+ }
342
+
343
+ /**
344
+ * Validate template syntax
345
+ */
346
+ validateSyntax(parseResult) {
347
+ const { expressions } = parseResult;
348
+ const errors = [];
349
+ const stack = [];
350
+
351
+ // Check for balanced expressions
352
+ expressions.forEach((expr, index) => {
353
+ switch (expr.type) {
354
+ case 'if':
355
+ stack.push({ type: 'if', index });
356
+ break;
357
+ case 'endif':
358
+ if (stack.length === 0 || stack[stack.length - 1].type !== 'if') {
359
+ errors.push(`Unmatched {% endif %} at position ${expr.position}`);
360
+ } else {
361
+ stack.pop();
362
+ }
363
+ break;
364
+ case 'for':
365
+ stack.push({ type: 'for', index });
366
+ break;
367
+ case 'endfor':
368
+ if (stack.length === 0 || stack[stack.length - 1].type !== 'for') {
369
+ errors.push(`Unmatched {% endfor %} at position ${expr.position}`);
370
+ } else {
371
+ stack.pop();
372
+ }
373
+ break;
374
+ case 'block':
375
+ stack.push({ type: 'block', index });
376
+ break;
377
+ case 'endblock':
378
+ if (stack.length === 0 || stack[stack.length - 1].type !== 'block') {
379
+ errors.push(`Unmatched {% endblock %} at position ${expr.position}`);
380
+ } else {
381
+ stack.pop();
382
+ }
383
+ break;
384
+ }
385
+ });
386
+
387
+ // Check for unclosed expressions
388
+ stack.forEach(unclosed => {
389
+ const expr = expressions[unclosed.index];
390
+ errors.push(`Unclosed {% ${expr.type} %} at position ${expr.position}`);
391
+ });
392
+
393
+ // Check depth limits
394
+ if (parseResult.structure.depth > this.options.maxDepth) {
395
+ errors.push(`Template nesting depth ${parseResult.structure.depth} exceeds maximum ${this.options.maxDepth}`);
396
+ }
397
+
398
+ if (errors.length > 0) {
399
+ throw new Error(`Syntax validation failed:\n${errors.join('\n')}`);
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Check if template is deterministic
405
+ */
406
+ isDeterministic(parseResult) {
407
+ const { variables, expressions } = parseResult;
408
+
409
+ // Check for non-deterministic variables
410
+ const nonDeterministicVars = variables.filter(name =>
411
+ ['now', 'random', 'uuid'].includes(name)
412
+ );
413
+
414
+ if (nonDeterministicVars.length > 0) {
415
+ return {
416
+ deterministic: false,
417
+ reasons: [`Non-deterministic variables: ${nonDeterministicVars.join(', ')}`]
418
+ };
419
+ }
420
+
421
+ // Check for non-deterministic expressions
422
+ const nonDeterministicExpressions = expressions.filter(expr =>
423
+ /\b(now|random|uuid)\b/.test(expr.expression)
424
+ );
425
+
426
+ if (nonDeterministicExpressions.length > 0) {
427
+ return {
428
+ deterministic: false,
429
+ reasons: [`Non-deterministic expressions found`]
430
+ };
431
+ }
432
+
433
+ return { deterministic: true, reasons: [] };
434
+ }
435
+
436
+ /**
437
+ * Get parser statistics
438
+ */
439
+ getStats() {
440
+ return {
441
+ ...this.options,
442
+ supportedPatterns: Object.keys(this.patterns)
443
+ };
444
+ }
445
+ }
446
+
447
+ export default KGenParser;