@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,313 @@
1
+ /**
2
+ * KGEN Post-Processor - Normalize and clean rendered output
3
+ *
4
+ * Handles:
5
+ * - Whitespace normalization
6
+ * - Line ending consistency
7
+ * - Trailing whitespace removal
8
+ * - Final newline ensuring
9
+ * - Deterministic formatting
10
+ */
11
+
12
+ export class KGenPostProcessor {
13
+ constructor(options = {}) {
14
+ this.options = {
15
+ normalizeWhitespace: options.normalizeWhitespace !== false,
16
+ trimLines: options.trimLines !== false,
17
+ ensureFinalNewline: options.ensureFinalNewline !== false,
18
+ normalizeLineEndings: options.normalizeLineEndings !== false,
19
+ removeEmptyLines: options.removeEmptyLines === true,
20
+ indentSize: options.indentSize || 2,
21
+ deterministicMode: options.deterministicMode !== false,
22
+ ...options
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Process rendered content for consistency
28
+ */
29
+ async process(content, options = {}) {
30
+ if (!content || typeof content !== 'string') {
31
+ return {
32
+ content: '',
33
+ metadata: {
34
+ processed: true,
35
+ originalLength: 0,
36
+ finalLength: 0,
37
+ changes: []
38
+ }
39
+ };
40
+ }
41
+
42
+ const processingOptions = { ...this.options, ...options };
43
+ let processed = content;
44
+ const changes = [];
45
+ const originalLength = content.length;
46
+
47
+ // 1. Normalize line endings to Unix style
48
+ if (processingOptions.normalizeLineEndings) {
49
+ const beforeLength = processed.length;
50
+ processed = processed.replace(/\r\n|\r/g, '\n');
51
+ if (processed.length !== beforeLength) {
52
+ changes.push('normalized-line-endings');
53
+ }
54
+ }
55
+
56
+ // 2. Remove trailing whitespace from lines
57
+ if (processingOptions.trimLines) {
58
+ const beforeLength = processed.length;
59
+ processed = processed.replace(/[^\S\n]+$/gm, '');
60
+ if (processed.length !== beforeLength) {
61
+ changes.push('trimmed-lines');
62
+ }
63
+ }
64
+
65
+ // 3. Remove completely empty lines if requested
66
+ if (processingOptions.removeEmptyLines) {
67
+ const beforeLength = processed.length;
68
+ processed = processed.replace(/\n\s*\n\s*\n/g, '\n\n');
69
+ if (processed.length !== beforeLength) {
70
+ changes.push('removed-empty-lines');
71
+ }
72
+ }
73
+
74
+ // 4. Normalize whitespace patterns
75
+ if (processingOptions.normalizeWhitespace) {
76
+ const beforeLength = processed.length;
77
+
78
+ // Normalize multiple spaces to single space (but preserve intentional indentation)
79
+ processed = processed.replace(/[^\S\n]{2,}/g, match => {
80
+ // If it's at the start of a line, preserve it (it's indentation)
81
+ const lines = processed.split('\n');
82
+ for (const line of lines) {
83
+ if (line.startsWith(match)) {
84
+ return match; // Preserve indentation
85
+ }
86
+ }
87
+ return ' '; // Replace multiple spaces with single space
88
+ });
89
+
90
+ if (processed.length !== beforeLength) {
91
+ changes.push('normalized-whitespace');
92
+ }
93
+ }
94
+
95
+ // 5. Ensure consistent final newline
96
+ if (processingOptions.ensureFinalNewline) {
97
+ if (processed.length > 0 && !processed.endsWith('\n')) {
98
+ processed += '\n';
99
+ changes.push('added-final-newline');
100
+ }
101
+ }
102
+
103
+ // 6. Deterministic formatting adjustments
104
+ if (processingOptions.deterministicMode) {
105
+ processed = this.applyDeterministicFormatting(processed, changes);
106
+ }
107
+
108
+ return {
109
+ content: processed,
110
+ metadata: {
111
+ processed: true,
112
+ originalLength,
113
+ finalLength: processed.length,
114
+ changes,
115
+ options: processingOptions,
116
+ timestamp: processingOptions.deterministicMode ?
117
+ '2024-01-01T00:00:00.000Z' :
118
+ new Date().toISOString()
119
+ }
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Apply deterministic formatting rules
125
+ */
126
+ applyDeterministicFormatting(content, changes) {
127
+ let processed = content;
128
+
129
+ // Ensure consistent indentation
130
+ const lines = processed.split('\n');
131
+ const processedLines = lines.map(line => {
132
+ if (line.trim() === '') return ''; // Empty lines have no whitespace
133
+
134
+ // Detect current indentation
135
+ const indentMatch = line.match(/^(\s*)/);
136
+ const currentIndent = indentMatch ? indentMatch[1] : '';
137
+
138
+ // Convert tabs to spaces for consistency
139
+ if (currentIndent.includes('\t')) {
140
+ const spaceIndent = currentIndent.replace(/\t/g, ' '.repeat(this.options.indentSize));
141
+ const newLine = spaceIndent + line.substring(currentIndent.length);
142
+ changes.push('normalized-indentation');
143
+ return newLine;
144
+ }
145
+
146
+ return line;
147
+ });
148
+
149
+ processed = processedLines.join('\n');
150
+
151
+ return processed;
152
+ }
153
+
154
+ /**
155
+ * Validate output consistency
156
+ */
157
+ async validate(content) {
158
+ const issues = [];
159
+
160
+ if (typeof content !== 'string') {
161
+ issues.push({ type: 'invalid-type', message: 'Content must be a string' });
162
+ return { valid: false, issues };
163
+ }
164
+
165
+ // Check for mixed line endings
166
+ if (content.includes('\r\n') || content.includes('\r')) {
167
+ issues.push({ type: 'mixed-line-endings', message: 'Mixed line endings detected' });
168
+ }
169
+
170
+ // Check for trailing whitespace
171
+ const lines = content.split('\n');
172
+ lines.forEach((line, index) => {
173
+ if (line.match(/\s+$/)) {
174
+ issues.push({
175
+ type: 'trailing-whitespace',
176
+ message: `Trailing whitespace on line ${index + 1}`,
177
+ line: index + 1
178
+ });
179
+ }
180
+ });
181
+
182
+ // Check for inconsistent indentation
183
+ let hasSpaces = false;
184
+ let hasTabs = false;
185
+
186
+ lines.forEach((line, index) => {
187
+ const indentMatch = line.match(/^(\s*)/);
188
+ if (indentMatch && indentMatch[1]) {
189
+ if (indentMatch[1].includes(' ')) hasSpaces = true;
190
+ if (indentMatch[1].includes('\t')) hasTabs = true;
191
+ }
192
+ });
193
+
194
+ if (hasSpaces && hasTabs) {
195
+ issues.push({
196
+ type: 'mixed-indentation',
197
+ message: 'Mixed spaces and tabs for indentation'
198
+ });
199
+ }
200
+
201
+ return {
202
+ valid: issues.length === 0,
203
+ issues,
204
+ stats: {
205
+ lines: lines.length,
206
+ hasTrailingWhitespace: issues.some(i => i.type === 'trailing-whitespace'),
207
+ hasMixedIndentation: issues.some(i => i.type === 'mixed-indentation'),
208
+ hasMixedLineEndings: issues.some(i => i.type === 'mixed-line-endings')
209
+ }
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Compare two content strings for consistency
215
+ */
216
+ async compare(content1, content2) {
217
+ const processed1 = await this.process(content1);
218
+ const processed2 = await this.process(content2);
219
+
220
+ const identical = processed1.content === processed2.content;
221
+
222
+ return {
223
+ identical,
224
+ content1Length: processed1.metadata.finalLength,
225
+ content2Length: processed2.metadata.finalLength,
226
+ changes1: processed1.metadata.changes,
227
+ changes2: processed2.metadata.changes,
228
+ firstDifference: identical ? null : this.findFirstDifference(processed1.content, processed2.content)
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Find first character difference between strings
234
+ */
235
+ findFirstDifference(str1, str2) {
236
+ const minLength = Math.min(str1.length, str2.length);
237
+
238
+ for (let i = 0; i < minLength; i++) {
239
+ if (str1[i] !== str2[i]) {
240
+ return {
241
+ position: i,
242
+ char1: str1[i],
243
+ char2: str2[i],
244
+ context: {
245
+ before: str1.substring(Math.max(0, i - 10), i),
246
+ after: str1.substring(i + 1, i + 11)
247
+ }
248
+ };
249
+ }
250
+ }
251
+
252
+ // One string is longer than the other
253
+ if (str1.length !== str2.length) {
254
+ return {
255
+ position: minLength,
256
+ char1: str1[minLength] || '<end>',
257
+ char2: str2[minLength] || '<end>',
258
+ lengthDifference: str1.length - str2.length
259
+ };
260
+ }
261
+
262
+ return null; // No differences found
263
+ }
264
+
265
+ /**
266
+ * Create deterministic content hash
267
+ */
268
+ createContentHash(content) {
269
+ const crypto = require('crypto');
270
+ return crypto.createHash('sha256').update(content || '', 'utf8').digest('hex');
271
+ }
272
+
273
+ /**
274
+ * Get processor statistics
275
+ */
276
+ getStats() {
277
+ return {
278
+ ...this.options,
279
+ version: '2.0.0-kgen-native',
280
+ supportedOperations: [
281
+ 'normalize-line-endings',
282
+ 'trim-lines',
283
+ 'remove-empty-lines',
284
+ 'normalize-whitespace',
285
+ 'ensure-final-newline',
286
+ 'deterministic-formatting'
287
+ ]
288
+ };
289
+ }
290
+
291
+ /**
292
+ * Benchmark processing performance
293
+ */
294
+ async benchmark(content, iterations = 100) {
295
+ const start = Date.now();
296
+
297
+ for (let i = 0; i < iterations; i++) {
298
+ await this.process(content);
299
+ }
300
+
301
+ const duration = Date.now() - start;
302
+
303
+ return {
304
+ iterations,
305
+ totalTime: duration,
306
+ averageTime: duration / iterations,
307
+ contentLength: content?.length || 0,
308
+ throughput: Math.round((content?.length || 0) * iterations / duration * 1000) // chars per second
309
+ };
310
+ }
311
+ }
312
+
313
+ export default KGenPostProcessor;