@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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/package.json +90 -0
- package/src/MIGRATION_COMPLETE.md +186 -0
- package/src/PORT-MAP.md +302 -0
- package/src/base/filter-templates.js +479 -0
- package/src/base/index.js +92 -0
- package/src/base/injection-targets.js +583 -0
- package/src/base/macro-templates.js +298 -0
- package/src/base/macro-templates.js.bak +461 -0
- package/src/base/shacl-templates.js +617 -0
- package/src/base/template-base.js +388 -0
- package/src/core/attestor.js +381 -0
- package/src/core/filters.js +518 -0
- package/src/core/index.js +21 -0
- package/src/core/kgen-engine.js +372 -0
- package/src/core/parser.js +447 -0
- package/src/core/post-processor.js +313 -0
- package/src/core/renderer.js +469 -0
- package/src/doc-generator/cli.mjs +122 -0
- package/src/doc-generator/index.mjs +28 -0
- package/src/doc-generator/mdx-generator.mjs +71 -0
- package/src/doc-generator/nav-generator.mjs +136 -0
- package/src/doc-generator/parser.mjs +291 -0
- package/src/doc-generator/rdf-builder.mjs +306 -0
- package/src/doc-generator/scanner.mjs +189 -0
- package/src/engine/index.js +42 -0
- package/src/engine/pipeline.js +448 -0
- package/src/engine/renderer.js +604 -0
- package/src/engine/template-engine.js +566 -0
- package/src/filters/array.js +436 -0
- package/src/filters/data.js +479 -0
- package/src/filters/index.js +270 -0
- package/src/filters/rdf.js +264 -0
- package/src/filters/text.js +369 -0
- package/src/index.js +109 -0
- package/src/inheritance/index.js +40 -0
- package/src/injection/api.js +260 -0
- package/src/injection/atomic-writer.js +327 -0
- package/src/injection/constants.js +136 -0
- package/src/injection/idempotency-manager.js +295 -0
- package/src/injection/index.js +28 -0
- package/src/injection/injection-engine.js +378 -0
- package/src/injection/integration.js +339 -0
- package/src/injection/modes/index.js +341 -0
- package/src/injection/rollback-manager.js +373 -0
- package/src/injection/target-resolver.js +323 -0
- package/src/injection/tests/atomic-writer.test.js +382 -0
- package/src/injection/tests/injection-engine.test.js +611 -0
- package/src/injection/tests/integration.test.js +392 -0
- package/src/injection/tests/run-tests.js +283 -0
- package/src/injection/validation-engine.js +547 -0
- package/src/linter/determinism-linter.js +473 -0
- package/src/linter/determinism.js +410 -0
- package/src/linter/index.js +6 -0
- package/src/linter/test-doubles.js +475 -0
- package/src/parser/frontmatter.js +228 -0
- package/src/parser/variables.js +344 -0
- package/src/renderer/deterministic.js +245 -0
- package/src/renderer/index.js +6 -0
- package/src/templates/latex/academic-paper.njk +186 -0
- package/src/templates/latex/index.js +104 -0
- package/src/templates/nextjs/app-page.njk +66 -0
- package/src/templates/nextjs/index.js +80 -0
- package/src/templates/office/docx/document.njk +368 -0
- package/src/templates/office/index.js +79 -0
- package/src/templates/office/word-report.njk +129 -0
- 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;
|