eyeling 1.26.7 → 1.27.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/HANDBOOK.md +32 -1
- package/dist/browser/eyeling.browser.js +519 -3
- package/examples/deck/act-barley-seed-lineage.md +1 -1
- package/examples/deck/faltings-genus2-finiteness.md +1 -1
- package/examples/deck/high-trust-rdf-bloom-envelope.md +1 -1
- package/examples/deck/odrl-dpv-risk-ranked.md +1 -1
- package/examples/deck/rdf-message-cold-chain-recall.md +71 -0
- package/examples/deck/rdf-message-flow.md +1 -1
- package/examples/deck/rdf-message-ldes-incremental.md +94 -0
- package/examples/deck/rdf-message-window-repair.md +1 -1
- package/examples/deck/schema-foaf-mapping.md +2 -0
- package/examples/input/rdf-message-cold-chain-recall.trig +668 -0
- package/examples/input/rdf-message-flow.trig +3 -0
- package/examples/input/rdf-message-ldes-incremental.trig +564 -0
- package/examples/input/rdf-message-microgrid.trig +3 -0
- package/examples/input/rdf-message-window-repair.trig +3 -0
- package/examples/input/rdf-messages.trig +3 -0
- package/examples/output/rdf-message-cold-chain-recall.md +13 -0
- package/examples/output/rdf-message-ldes-incremental.md +13 -0
- package/examples/rdf-message-cold-chain-recall.n3 +229 -0
- package/examples/rdf-message-flow.n3 +3 -0
- package/examples/rdf-message-ldes-incremental.n3 +231 -0
- package/examples/rdf-message-microgrid.n3 +3 -0
- package/examples/rdf-message-window-repair.n3 +3 -0
- package/examples/rdf-messages.n3 +3 -0
- package/eyeling.js +519 -3
- package/lib/cli.js +519 -3
- package/package.json +5 -3
- package/test/examples.test.js +25 -14
- package/test/fixtures/marc-rules-stream-messages.n3 +192 -0
- package/test/stream_messages.test.js +284 -0
package/lib/cli.js
CHANGED
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const os = require('node:os');
|
|
10
12
|
const path = require('node:path');
|
|
11
|
-
const { pathToFileURL } = require('node:url');
|
|
13
|
+
const { pathToFileURL, fileURLToPath } = require('node:url');
|
|
14
|
+
const { TextDecoder } = require('node:util');
|
|
12
15
|
|
|
13
16
|
const engine = require('./engine');
|
|
14
17
|
const deref = require('./deref');
|
|
15
18
|
const { PrefixEnv } = require('./prelude');
|
|
19
|
+
const { normalizeRdfCompatibility } = require('./lexer');
|
|
16
20
|
const { parseN3Text, mergeParsedDocuments } = require('./multisource');
|
|
17
21
|
|
|
18
22
|
function offsetToLineCol(text, offset) {
|
|
@@ -51,7 +55,6 @@ function formatN3SyntaxError(err, text, path) {
|
|
|
51
55
|
|
|
52
56
|
// CLI entry point (invoked when eyeling.js is run directly)
|
|
53
57
|
function readTextFromStdinSync() {
|
|
54
|
-
const fs = require('node:fs');
|
|
55
58
|
return fs.readFileSync(0, { encoding: 'utf8' });
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -59,6 +62,10 @@ function __isNetworkOrFileIri(s) {
|
|
|
59
62
|
return typeof s === 'string' && /^(https?:|file:\/\/)/i.test(s);
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
function __isHttpSource(s) {
|
|
66
|
+
return typeof s === 'string' && /^https?:/i.test(s);
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
function __sourceLabelToBaseIri(sourceLabel) {
|
|
63
70
|
if (!sourceLabel || sourceLabel === '<stdin>') return '';
|
|
64
71
|
if (__isNetworkOrFileIri(sourceLabel)) return deref.stripFragment(sourceLabel);
|
|
@@ -74,10 +81,492 @@ function __readInputSourceSync(sourceLabel) {
|
|
|
74
81
|
return txt;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
const fs = require('node:fs');
|
|
78
84
|
return fs.readFileSync(sourceLabel, { encoding: 'utf8' });
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
function __httpFetchScriptBody({ prefixOnly = false } = {}) {
|
|
88
|
+
return `
|
|
89
|
+
const fs = require('fs');
|
|
90
|
+
const http = require('http');
|
|
91
|
+
const https = require('https');
|
|
92
|
+
const zlib = require('zlib');
|
|
93
|
+
const { URL } = require('url');
|
|
94
|
+
const urlArg = process.argv[1];
|
|
95
|
+
const outFile = process.argv[2] || '';
|
|
96
|
+
const limit = Math.max(1, Number(process.argv[3] || 65536));
|
|
97
|
+
const prefixOnly = ${prefixOnly ? 'true' : 'false'};
|
|
98
|
+
const maxRedirects = 10;
|
|
99
|
+
function requestUrl(u, redirects) {
|
|
100
|
+
if (redirects > maxRedirects) { console.error('Too many redirects'); process.exit(3); }
|
|
101
|
+
const parsed = new URL(u);
|
|
102
|
+
const mod = parsed.protocol === 'https:' ? https : parsed.protocol === 'http:' ? http : null;
|
|
103
|
+
if (!mod) { console.error('Unsupported protocol ' + parsed.protocol); process.exit(2); }
|
|
104
|
+
const headers = {
|
|
105
|
+
accept: 'text/n3, text/turtle, application/trig, application/n-triples, application/n-quads, text/plain;q=0.8, */*;q=0.01',
|
|
106
|
+
'accept-encoding': 'identity',
|
|
107
|
+
'user-agent': 'eyeling-rdf-message-stream'
|
|
108
|
+
};
|
|
109
|
+
if (prefixOnly) headers.range = 'bytes=0-' + String(limit - 1);
|
|
110
|
+
const req = mod.request({
|
|
111
|
+
protocol: parsed.protocol,
|
|
112
|
+
hostname: parsed.hostname,
|
|
113
|
+
port: parsed.port || undefined,
|
|
114
|
+
path: parsed.pathname + parsed.search,
|
|
115
|
+
headers,
|
|
116
|
+
}, (res) => {
|
|
117
|
+
const sc = res.statusCode || 0;
|
|
118
|
+
if (sc >= 300 && sc < 400 && res.headers && res.headers.location) {
|
|
119
|
+
const next = new URL(res.headers.location, u).toString();
|
|
120
|
+
res.resume();
|
|
121
|
+
return requestUrl(next, redirects + 1);
|
|
122
|
+
}
|
|
123
|
+
if (sc < 200 || sc >= 300) {
|
|
124
|
+
res.resume();
|
|
125
|
+
console.error('HTTP status ' + sc);
|
|
126
|
+
process.exit(4);
|
|
127
|
+
}
|
|
128
|
+
const enc = String((res.headers && res.headers['content-encoding']) || '').toLowerCase();
|
|
129
|
+
let body = res;
|
|
130
|
+
if (enc.includes('gzip')) body = res.pipe(zlib.createGunzip());
|
|
131
|
+
else if (enc.includes('deflate')) body = res.pipe(zlib.createInflate());
|
|
132
|
+
else if (enc.includes('br')) body = res.pipe(zlib.createBrotliDecompress());
|
|
133
|
+
if (prefixOnly) {
|
|
134
|
+
const chunks = [];
|
|
135
|
+
let bytes = 0;
|
|
136
|
+
let finished = false;
|
|
137
|
+
function finish() {
|
|
138
|
+
if (finished) return;
|
|
139
|
+
finished = true;
|
|
140
|
+
const buf = Buffer.concat(chunks, bytes).subarray(0, limit);
|
|
141
|
+
process.stdout.write(buf.toString('utf8'));
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
body.on('data', (chunk) => {
|
|
145
|
+
if (finished) return;
|
|
146
|
+
chunks.push(chunk);
|
|
147
|
+
bytes += chunk.length;
|
|
148
|
+
if (bytes >= limit) finish();
|
|
149
|
+
});
|
|
150
|
+
body.on('end', finish);
|
|
151
|
+
body.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(5); });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const out = fs.createWriteStream(outFile);
|
|
155
|
+
body.pipe(out);
|
|
156
|
+
body.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(5); });
|
|
157
|
+
out.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(6); });
|
|
158
|
+
out.on('finish', () => process.exit(0));
|
|
159
|
+
});
|
|
160
|
+
req.on('error', (e) => { console.error(e && e.message ? e.message : String(e)); process.exit(5); });
|
|
161
|
+
req.end();
|
|
162
|
+
}
|
|
163
|
+
requestUrl(urlArg, 0);
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function __readHttpPrefixSync(sourceLabel, byteLimit = 64 * 1024) {
|
|
168
|
+
const cp = require('node:child_process');
|
|
169
|
+
const r = cp.spawnSync(process.execPath, ['-e', __httpFetchScriptBody({ prefixOnly: true }), sourceLabel, '', String(byteLimit)], {
|
|
170
|
+
encoding: 'utf8',
|
|
171
|
+
maxBuffer: byteLimit + 16 * 1024,
|
|
172
|
+
});
|
|
173
|
+
if (r.status !== 0) throw new Error(`Failed to dereference ${sourceLabel}${r.stderr ? ': ' + String(r.stderr).trim() : ''}`);
|
|
174
|
+
return r.stdout;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function __downloadHttpSourceToTempFileSync(sourceLabel) {
|
|
178
|
+
const cp = require('node:child_process');
|
|
179
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-rdf-message-log-'));
|
|
180
|
+
const file = path.join(dir, 'source.txt');
|
|
181
|
+
const r = cp.spawnSync(process.execPath, ['-e', __httpFetchScriptBody({ prefixOnly: false }), sourceLabel, file], {
|
|
182
|
+
encoding: 'utf8',
|
|
183
|
+
maxBuffer: 1024 * 1024,
|
|
184
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
185
|
+
});
|
|
186
|
+
if (r.status !== 0) {
|
|
187
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
|
|
188
|
+
throw new Error(`Failed to dereference ${sourceLabel}${r.stderr ? ': ' + String(r.stderr).trim() : ''}`);
|
|
189
|
+
}
|
|
190
|
+
return { file, cleanup: () => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} } };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
const RDF_MESSAGE_VERSION_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im;
|
|
195
|
+
const RDF_MESSAGE_VERSION_LINE_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/i;
|
|
196
|
+
const RDF_DIRECTIVE_LINE_RE = /^\s*(?:@?(?:prefix|base)\b|PREFIX\b|BASE\b)/i;
|
|
197
|
+
const RDF_MESSAGE_DELIMITER_LINE_RE = /^\s*(?:MESSAGE\b|@message\s*\.?)\s*(?:#.*)?$/i;
|
|
198
|
+
const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
|
|
199
|
+
const RDF_TYPE_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
|
|
200
|
+
const XSD_INTEGER_IRI = '<http://www.w3.org/2001/XMLSchema#integer>';
|
|
201
|
+
const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
|
|
202
|
+
const EYMSG_IRIS = Object.freeze({
|
|
203
|
+
RDFMessageStream: `<${EYMSG_NS}RDFMessageStream>`,
|
|
204
|
+
MessageEnvelope: `<${EYMSG_NS}MessageEnvelope>`,
|
|
205
|
+
envelope: `<${EYMSG_NS}envelope>`,
|
|
206
|
+
firstEnvelope: `<${EYMSG_NS}firstEnvelope>`,
|
|
207
|
+
lastEnvelope: `<${EYMSG_NS}lastEnvelope>`,
|
|
208
|
+
orderedEnvelopes: `<${EYMSG_NS}orderedEnvelopes>`,
|
|
209
|
+
offset: `<${EYMSG_NS}offset>`,
|
|
210
|
+
payloadGraph: `<${EYMSG_NS}payloadGraph>`,
|
|
211
|
+
payloadKind: `<${EYMSG_NS}payloadKind>`,
|
|
212
|
+
empty: `<${EYMSG_NS}empty>`,
|
|
213
|
+
nonEmpty: `<${EYMSG_NS}nonEmpty>`,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
function simpleHashText(s) {
|
|
217
|
+
let h = 0x811c9dc5;
|
|
218
|
+
const text = String(s || '');
|
|
219
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
220
|
+
h ^= text.charCodeAt(i);
|
|
221
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
222
|
+
}
|
|
223
|
+
return h.toString(16).padStart(8, '0');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function __isLocalPathSource(sourceLabel) {
|
|
227
|
+
return typeof sourceLabel === 'string' && sourceLabel !== '<stdin>' && !/^(https?:|file:\/\/)/i.test(sourceLabel);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function __localPathForSource(sourceLabel) {
|
|
231
|
+
if (__isLocalPathSource(sourceLabel)) return sourceLabel;
|
|
232
|
+
if (typeof sourceLabel === 'string' && /^file:\/\//i.test(sourceLabel)) return fileURLToPath(sourceLabel);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function __sourceLooksLikeRdfMessageLogSync(sourceLabel) {
|
|
237
|
+
const filePath = __localPathForSource(sourceLabel);
|
|
238
|
+
if (filePath) {
|
|
239
|
+
const fd = fs.openSync(filePath, 'r');
|
|
240
|
+
try {
|
|
241
|
+
const buf = Buffer.allocUnsafe(64 * 1024);
|
|
242
|
+
const n = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
243
|
+
return RDF_MESSAGE_VERSION_RE.test(buf.toString('utf8', 0, n));
|
|
244
|
+
} finally {
|
|
245
|
+
fs.closeSync(fd);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (__isHttpSource(sourceLabel)) {
|
|
249
|
+
return RDF_MESSAGE_VERSION_RE.test(__readHttpPrefixSync(sourceLabel));
|
|
250
|
+
}
|
|
251
|
+
return RDF_MESSAGE_VERSION_RE.test(__readInputSourceSync(sourceLabel));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function stripRdfDirectiveLines(text) {
|
|
255
|
+
return String(text || '')
|
|
256
|
+
.split(/(?<=\r\n|\n|\r)/)
|
|
257
|
+
.filter((line) => !RDF_DIRECTIVE_LINE_RE.test(line) && !RDF_MESSAGE_VERSION_LINE_RE.test(line))
|
|
258
|
+
.join('');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function hasRdfPayload(text) {
|
|
262
|
+
return String(text || '')
|
|
263
|
+
.split(/\r\n|\n|\r/)
|
|
264
|
+
.some((line) => {
|
|
265
|
+
const trimmed = line.replace(/#.*$/g, '').trim();
|
|
266
|
+
return trimmed && !RDF_DIRECTIVE_LINE_RE.test(trimmed) && !RDF_MESSAGE_VERSION_LINE_RE.test(trimmed);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function addRdfDirective(directives, seen, line) {
|
|
271
|
+
if (!RDF_DIRECTIVE_LINE_RE.test(line)) return;
|
|
272
|
+
const key = line.trim();
|
|
273
|
+
if (!key || seen.has(key)) return;
|
|
274
|
+
seen.add(key);
|
|
275
|
+
directives.push(line.endsWith('\n') || line.endsWith('\r') ? line : line + '\n');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeStreamingPayloadChunk(chunk, directives) {
|
|
279
|
+
const prelude = directives.join('');
|
|
280
|
+
const normalized = normalizeRdfCompatibility(prelude + String(chunk || ''));
|
|
281
|
+
return stripRdfDirectiveLines(normalized).trim();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function buildSingleMessageReplayDocument({ sourceLabel, messageIndex, chunk, directives }) {
|
|
285
|
+
const hash = simpleHashText(sourceLabel || '<stream>');
|
|
286
|
+
const base = `urn:eyeling:message-stream:${hash}`;
|
|
287
|
+
const padded = String(messageIndex).padStart(6, '0');
|
|
288
|
+
const stream = `<${base}#stream>`;
|
|
289
|
+
const envelope = `<${base}#m${padded}>`;
|
|
290
|
+
const payload = `<${base}#m${padded}/payload>`;
|
|
291
|
+
const body = normalizeStreamingPayloadChunk(chunk, directives);
|
|
292
|
+
const hasBody = hasRdfPayload(body);
|
|
293
|
+
const out = [];
|
|
294
|
+
|
|
295
|
+
out.push(...directives.map((line) => line.trim()).filter(Boolean));
|
|
296
|
+
out.push(`${stream} ${RDF_TYPE_IRI} ${EYMSG_IRIS.RDFMessageStream} .`);
|
|
297
|
+
out.push(`${stream} ${EYMSG_IRIS.envelope} ${envelope} .`);
|
|
298
|
+
out.push(`${stream} ${EYMSG_IRIS.orderedEnvelopes} (${envelope}) .`);
|
|
299
|
+
out.push(`${stream} ${EYMSG_IRIS.firstEnvelope} ${envelope} .`);
|
|
300
|
+
out.push(`${stream} ${EYMSG_IRIS.lastEnvelope} ${envelope} .`);
|
|
301
|
+
out.push(`${envelope} ${RDF_TYPE_IRI} ${EYMSG_IRIS.MessageEnvelope} .`);
|
|
302
|
+
out.push(`${envelope} ${EYMSG_IRIS.offset} "${messageIndex}"^^${XSD_INTEGER_IRI} .`);
|
|
303
|
+
out.push(`${envelope} ${EYMSG_IRIS.payloadKind} ${hasBody ? EYMSG_IRIS.nonEmpty : EYMSG_IRIS.empty} .`);
|
|
304
|
+
if (hasBody) {
|
|
305
|
+
out.push(`${envelope} ${EYMSG_IRIS.payloadGraph} ${payload} .`);
|
|
306
|
+
out.push(`${payload} ${LOG_NAME_OF_IRI} {`);
|
|
307
|
+
out.push(body);
|
|
308
|
+
out.push(`} .`);
|
|
309
|
+
}
|
|
310
|
+
return out.join('\n') + '\n';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function forEachRdfMessageChunkInText(text, onMessage) {
|
|
314
|
+
const directives = [];
|
|
315
|
+
const seenDirectives = new Set();
|
|
316
|
+
let chunk = '';
|
|
317
|
+
let messageIndex = 1;
|
|
318
|
+
let sawVersion = false;
|
|
319
|
+
let sawDelimiter = false;
|
|
320
|
+
|
|
321
|
+
function emit() {
|
|
322
|
+
onMessage({ messageIndex, chunk, directives: directives.slice() });
|
|
323
|
+
messageIndex += 1;
|
|
324
|
+
chunk = '';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const lines = String(text || '').match(/.*(?:\r\n|\n|\r)|.+$/g) || [];
|
|
328
|
+
for (const line of lines) {
|
|
329
|
+
if (RDF_MESSAGE_VERSION_LINE_RE.test(line)) {
|
|
330
|
+
sawVersion = true;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (RDF_MESSAGE_DELIMITER_LINE_RE.test(line)) {
|
|
334
|
+
emit();
|
|
335
|
+
sawDelimiter = true;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
addRdfDirective(directives, seenDirectives, line);
|
|
339
|
+
chunk += line;
|
|
340
|
+
}
|
|
341
|
+
if (!sawVersion) throw new Error('not an RDF Message Log: missing VERSION "*-messages" directive');
|
|
342
|
+
if (sawDelimiter || hasRdfPayload(chunk)) emit();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function forEachLineInFileSync(filePath, onLine) {
|
|
346
|
+
const fd = fs.openSync(filePath, 'r');
|
|
347
|
+
const decoder = new TextDecoder('utf8');
|
|
348
|
+
const buf = Buffer.allocUnsafe(64 * 1024);
|
|
349
|
+
let carry = '';
|
|
350
|
+
try {
|
|
351
|
+
for (;;) {
|
|
352
|
+
const n = fs.readSync(fd, buf, 0, buf.length, null);
|
|
353
|
+
if (n === 0) break;
|
|
354
|
+
carry += decoder.decode(buf.subarray(0, n), { stream: true });
|
|
355
|
+
for (;;) {
|
|
356
|
+
const m = /\r\n|\n|\r/.exec(carry);
|
|
357
|
+
if (!m) break;
|
|
358
|
+
const end = m.index + m[0].length;
|
|
359
|
+
onLine(carry.slice(0, end));
|
|
360
|
+
carry = carry.slice(end);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
carry += decoder.decode();
|
|
364
|
+
if (carry) onLine(carry);
|
|
365
|
+
} finally {
|
|
366
|
+
fs.closeSync(fd);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function forEachRdfMessageChunkInFileSync(filePath, onMessage) {
|
|
371
|
+
const directives = [];
|
|
372
|
+
const seenDirectives = new Set();
|
|
373
|
+
let chunk = '';
|
|
374
|
+
let messageIndex = 1;
|
|
375
|
+
let sawVersion = false;
|
|
376
|
+
let sawDelimiter = false;
|
|
377
|
+
|
|
378
|
+
function emit() {
|
|
379
|
+
onMessage({ messageIndex, chunk, directives: directives.slice() });
|
|
380
|
+
messageIndex += 1;
|
|
381
|
+
chunk = '';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
forEachLineInFileSync(filePath, (line) => {
|
|
385
|
+
if (RDF_MESSAGE_VERSION_LINE_RE.test(line)) {
|
|
386
|
+
sawVersion = true;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (RDF_MESSAGE_DELIMITER_LINE_RE.test(line)) {
|
|
390
|
+
emit();
|
|
391
|
+
sawDelimiter = true;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
addRdfDirective(directives, seenDirectives, line);
|
|
395
|
+
chunk += line;
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
if (!sawVersion) throw new Error('not an RDF Message Log: missing VERSION "*-messages" directive');
|
|
399
|
+
if (sawDelimiter || hasRdfPayload(chunk)) emit();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
function __forEachRdfMessageChunkSync(sourceLabel, onMessage) {
|
|
404
|
+
const filePath = __localPathForSource(sourceLabel);
|
|
405
|
+
if (filePath) {
|
|
406
|
+
forEachRdfMessageChunkInFileSync(filePath, onMessage);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (__isHttpSource(sourceLabel)) {
|
|
410
|
+
const tmp = __downloadHttpSourceToTempFileSync(sourceLabel);
|
|
411
|
+
try {
|
|
412
|
+
forEachRdfMessageChunkInFileSync(tmp.file, onMessage);
|
|
413
|
+
} finally {
|
|
414
|
+
tmp.cleanup();
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
forEachRdfMessageChunkInText(__readInputSourceSync(sourceLabel), onMessage);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function factsContainOutputStrings(triplesForOutput) {
|
|
422
|
+
const LOG_OUTPUT_STRING = 'http://www.w3.org/2000/10/swap/log#outputString';
|
|
423
|
+
return (
|
|
424
|
+
Array.isArray(triplesForOutput) &&
|
|
425
|
+
triplesForOutput.some(
|
|
426
|
+
(tr) => tr && tr.p && tr.p.constructor && tr.p.constructor.name === 'Iri' && tr.p.value === LOG_OUTPUT_STRING,
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function programMayProduceOutputStrings(topLevelTriples, forwardRules, logQueryRules) {
|
|
432
|
+
const hasOutputStringPredicate = (trs) => factsContainOutputStrings(trs);
|
|
433
|
+
if (hasOutputStringPredicate(topLevelTriples)) return true;
|
|
434
|
+
if (Array.isArray(forwardRules) && forwardRules.some((r) => hasOutputStringPredicate(r && r.conclusion))) return true;
|
|
435
|
+
if (Array.isArray(logQueryRules) && logQueryRules.some((r) => hasOutputStringPredicate(r && r.conclusion))) return true;
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes = null } = {}) {
|
|
440
|
+
const prefixes = mergedDocument.prefixes;
|
|
441
|
+
const triples = mergedDocument.triples;
|
|
442
|
+
const frules = mergedDocument.frules;
|
|
443
|
+
const brules = mergedDocument.brules;
|
|
444
|
+
const qrules = mergedDocument.logQueryRules;
|
|
445
|
+
|
|
446
|
+
engine.materializeRdfLists(triples, frules.concat(qrules || []), brules);
|
|
447
|
+
const facts = triples.slice();
|
|
448
|
+
const hasQueries = Array.isArray(qrules) && qrules.length;
|
|
449
|
+
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
450
|
+
|
|
451
|
+
let derived = [];
|
|
452
|
+
let outTriples = [];
|
|
453
|
+
if (hasQueries) {
|
|
454
|
+
const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules, { prefixes });
|
|
455
|
+
derived = res.derived;
|
|
456
|
+
outTriples = res.queryTriples;
|
|
457
|
+
} else {
|
|
458
|
+
const skipDerivedCollection = mayAutoRenderOutputStrings;
|
|
459
|
+
derived = engine.forwardChain(facts, frules, brules, null, {
|
|
460
|
+
captureExplanations: false,
|
|
461
|
+
collectDerived: !skipDerivedCollection,
|
|
462
|
+
prefixes,
|
|
463
|
+
});
|
|
464
|
+
outTriples = skipDerivedCollection ? [] : derived.map((df) => df.fact);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const renderedOutputTriples = hasQueries ? outTriples : facts;
|
|
468
|
+
if (factsContainOutputStrings(renderedOutputTriples)) {
|
|
469
|
+
process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const outPrefixEnv = outputPrefixes || prefixes;
|
|
474
|
+
if (rdfMode) {
|
|
475
|
+
for (const tr of outTriples) console.log(engine.tripleToRdfCompatible(tr, outPrefixEnv));
|
|
476
|
+
} else if (hasQueries) {
|
|
477
|
+
const bodyText = engine.prettyPrintQueryTriples(outTriples, outPrefixEnv);
|
|
478
|
+
if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
|
|
479
|
+
} else {
|
|
480
|
+
for (const df of derived) console.log(engine.tripleToN3(df.fact, outPrefixEnv));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function runStreamMessagesMode(sourceLabels, { rdfMode }) {
|
|
485
|
+
const ordinarySourceLabels = [];
|
|
486
|
+
const messageSourceLabels = [];
|
|
487
|
+
|
|
488
|
+
for (const sourceLabel of sourceLabels) {
|
|
489
|
+
try {
|
|
490
|
+
if (__sourceLooksLikeRdfMessageLogSync(sourceLabel)) messageSourceLabels.push(sourceLabel);
|
|
491
|
+
else ordinarySourceLabels.push(sourceLabel);
|
|
492
|
+
} catch (e) {
|
|
493
|
+
console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e && e.message ? e.message : String(e)}`);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (!messageSourceLabels.length) {
|
|
499
|
+
console.error('Error: --stream-messages did not find any RDF Message Log input.');
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const programSources = [];
|
|
504
|
+
for (const sourceLabel of ordinarySourceLabels) {
|
|
505
|
+
let text;
|
|
506
|
+
try {
|
|
507
|
+
text = __readInputSourceSync(sourceLabel);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
510
|
+
else console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
programSources.push(
|
|
516
|
+
parseN3Text(text, {
|
|
517
|
+
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
518
|
+
label: sourceLabel,
|
|
519
|
+
keepSourceArtifacts: false,
|
|
520
|
+
sourceLocations: false,
|
|
521
|
+
rdf: rdfMode,
|
|
522
|
+
}),
|
|
523
|
+
);
|
|
524
|
+
} catch (e) {
|
|
525
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
526
|
+
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
throw e;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const fullIriPrefixes = new PrefixEnv({});
|
|
534
|
+
for (const messageSourceLabel of messageSourceLabels) {
|
|
535
|
+
try {
|
|
536
|
+
__forEachRdfMessageChunkSync(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
|
|
537
|
+
const messageText = buildSingleMessageReplayDocument({
|
|
538
|
+
sourceLabel: messageSourceLabel,
|
|
539
|
+
messageIndex,
|
|
540
|
+
chunk,
|
|
541
|
+
directives,
|
|
542
|
+
});
|
|
543
|
+
let messageDoc;
|
|
544
|
+
try {
|
|
545
|
+
messageDoc = parseN3Text(messageText, {
|
|
546
|
+
baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
|
|
547
|
+
label: `${messageSourceLabel}#message-${messageIndex}`,
|
|
548
|
+
keepSourceArtifacts: false,
|
|
549
|
+
sourceLocations: false,
|
|
550
|
+
rdf: false,
|
|
551
|
+
});
|
|
552
|
+
} catch (e) {
|
|
553
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
554
|
+
console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
throw e;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
|
|
561
|
+
runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
|
|
562
|
+
});
|
|
563
|
+
} catch (e) {
|
|
564
|
+
console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
81
570
|
function main() {
|
|
82
571
|
// Drop "node" and script name; keep only user-provided args
|
|
83
572
|
// Expand combined short options: -pt == -p -t
|
|
@@ -108,6 +597,7 @@ function main() {
|
|
|
108
597
|
` -h, --help Show this help and exit.\n` +
|
|
109
598
|
` -p, --proof Enable proof explanations.\n` +
|
|
110
599
|
` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
|
|
600
|
+
` --stream-messages Process RDF Message Logs one message at a time under -r.\n` +
|
|
111
601
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
112
602
|
` -t, --stream Stream derived triples as soon as they are derived.\n` +
|
|
113
603
|
` -v, --version Print version and exit.\n`;
|
|
@@ -149,6 +639,7 @@ function main() {
|
|
|
149
639
|
|
|
150
640
|
const showAst = argv.includes('--ast') || argv.includes('-a');
|
|
151
641
|
const streamMode = argv.includes('--stream') || argv.includes('-t');
|
|
642
|
+
const streamMessagesMode = argv.includes('--stream-messages');
|
|
152
643
|
const rdfMode = argv.includes('--rdf') || argv.includes('-r');
|
|
153
644
|
|
|
154
645
|
// --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
|
|
@@ -171,6 +662,26 @@ function main() {
|
|
|
171
662
|
if (typeof engine.setSuperRestrictedMode === 'function') engine.setSuperRestrictedMode(true);
|
|
172
663
|
}
|
|
173
664
|
|
|
665
|
+
|
|
666
|
+
if (streamMessagesMode) {
|
|
667
|
+
if (!rdfMode) {
|
|
668
|
+
console.error('Error: --stream-messages requires -r/--rdf.');
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
if (showAst) {
|
|
672
|
+
console.error('Error: --stream-messages cannot be combined with --ast.');
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
if (streamMode) {
|
|
676
|
+
console.error('Error: --stream-messages cannot be combined with --stream.');
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
if (engine.getProofCommentsEnabled()) {
|
|
680
|
+
console.error('Error: --stream-messages currently does not support proof output.');
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
174
685
|
// Positional args (one or more N3 sources).
|
|
175
686
|
const useImplicitStdin = positional.length === 0 && !process.stdin.isTTY;
|
|
176
687
|
if (positional.length === 0 && !useImplicitStdin) {
|
|
@@ -194,6 +705,11 @@ function main() {
|
|
|
194
705
|
process.exit(1);
|
|
195
706
|
}
|
|
196
707
|
|
|
708
|
+
if (streamMessagesMode) {
|
|
709
|
+
runStreamMessagesMode(sourceLabels, { rdfMode });
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
197
713
|
const parsedSources = [];
|
|
198
714
|
for (const sourceLabel of sourceLabels) {
|
|
199
715
|
let text;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyeling",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.1",
|
|
4
4
|
"description": "A minimal Notation3 (N3) reasoner in JavaScript.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -40,14 +40,16 @@
|
|
|
40
40
|
"prebuild": "eslint . --fix",
|
|
41
41
|
"build": "node tools/bundle.js",
|
|
42
42
|
"test:packlist": "node test/packlist.test.js",
|
|
43
|
-
"test:api": "node test/api.test.js",
|
|
43
|
+
"test:api": "node test/api.test.js && node test/stream_messages.test.js",
|
|
44
|
+
"test:stream-messages": "node test/stream_messages.test.js",
|
|
44
45
|
"test:builtins": "node test/builtins.test.js",
|
|
45
46
|
"test:examples": "node test/examples.test.js",
|
|
47
|
+
"test:examples:proof": "node test/examples.test.js --proof-only",
|
|
46
48
|
"test:manifest": "node test/manifest.test.js",
|
|
47
49
|
"test:playground": "node test/playground.test.js",
|
|
48
50
|
"test:package": "node test/package.test.js",
|
|
49
51
|
"pretest": "npm run build && npm run test:packlist",
|
|
50
|
-
"test": "npm run test:api && npm run test:builtins && npm run test:examples && npm run test:manifest && npm run test:playground",
|
|
52
|
+
"test": "npm run test:api && npm run test:builtins && npm run test:examples && npm run test:examples:proof && npm run test:manifest && npm run test:playground",
|
|
51
53
|
"posttest": "npm run test:package",
|
|
52
54
|
"preversion": "npm test",
|
|
53
55
|
"postversion": "git push origin HEAD --follow-tags"
|