eyeleng 1.0.6 → 1.0.7

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.
@@ -1,24 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const assert = require('node:assert/strict');
5
4
  const fs = require('node:fs');
6
5
  const path = require('node:path');
7
- const { colors: C, info, msTag, okLine, failLine, summaryLine } = require('./harness.js');
8
-
9
- const eyeleng = require('../src/index.js');
10
- const { tripleKey } = require('../src/term.js');
11
-
12
- const DEFAULT_MANIFEST = 'https://w3c.github.io/data-shapes/shacl12-test-suite/tests/rules/manifest-rules.ttl';
13
- const rootManifestUrl = process.env.EYELENG_SHACL12_RULES_MANIFEST || DEFAULT_MANIFEST;
14
- const fetchTimeoutMs = Number(process.env.EYELENG_SHACL12_FETCH_TIMEOUT_MS || 30000);
15
- const textCache = new Map();
16
-
17
-
18
- function isLikelyNetworkError(err) {
19
- const msg = String(err && (err.stack || err.message || err));
20
- return /fetch failed|ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|network|timed out|GET .* failed/i.test(msg);
21
- }
6
+ const { colors: C, info, summaryLine } = require('./harness.js');
7
+ const {
8
+ defaultShacl12RulesManifestUrl,
9
+ isLikelyNetworkError,
10
+ isW3cRequired,
11
+ runShacl12RulesManifest,
12
+ formatShacl12RulesProgressLine,
13
+ writeShacl12RulesEarlReport,
14
+ } = require('../src/shacl12RulesManifest.js');
15
+
16
+ const rootManifestUrl = process.env.EYELENG_SHACL12_RULES_MANIFEST || defaultShacl12RulesManifestUrl;
22
17
 
23
18
  function appendSummary(summary) {
24
19
  const file = process.env.EYELENG_TEST_SUMMARY_FILE;
@@ -27,226 +22,53 @@ function appendSummary(summary) {
27
22
  fs.appendFileSync(file, `${JSON.stringify(summary)}\n`, 'utf8');
28
23
  }
29
24
 
30
- function stripHash(url) {
31
- const parsed = new URL(url);
32
- parsed.hash = '';
33
- return parsed.href;
34
- }
35
-
36
- function resolveHref(baseUrl, href) {
37
- return new URL(href, baseUrl).href;
38
- }
39
-
40
- async function fetchText(url) {
41
- const normalized = stripHash(url);
42
- if (textCache.has(normalized)) return textCache.get(normalized);
43
-
44
- const controller = new AbortController();
45
- const timer = setTimeout(() => controller.abort(), fetchTimeoutMs);
46
- try {
47
- const response = await fetch(normalized, { signal: controller.signal });
48
- if (!response.ok) throw new Error(`GET ${normalized} failed: ${response.status} ${response.statusText}`);
49
- const text = await response.text();
50
- textCache.set(normalized, text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
51
- return textCache.get(normalized);
52
- } catch (err) {
53
- if (err && err.name === 'AbortError') throw new Error(`GET ${normalized} timed out after ${fetchTimeoutMs} ms`);
54
- throw err;
55
- } finally {
56
- clearTimeout(timer);
57
- }
58
- }
59
-
60
- function parseIncludedManifests(rootUrl, text) {
61
- const includeMatch = /mf:include\s*\(([\s\S]*?)\)\s*\./m.exec(text);
62
- if (!includeMatch) throw new Error(`No mf:include list found in ${rootUrl}`);
63
- return [...includeMatch[1].matchAll(/<([^>]+)>/g)].map((match) => resolveHref(rootUrl, match[1]));
64
- }
65
-
66
- function sectionName(manifestUrl) {
67
- const pieces = new URL(manifestUrl).pathname.split('/').filter(Boolean);
68
- if (pieces.length < 2) return manifestUrl;
69
- return pieces[pieces.length - 2];
70
- }
71
-
72
- function manifestStatements(text) {
73
- const statements = [];
74
- const re = /([^\s;]+)\s+rdf:type\s+srt:([A-Za-z0-9]+)\s*;([\s\S]*?)\n\s*\./g;
75
- let match;
76
- while ((match = re.exec(text)) !== null) {
77
- statements.push({ id: match[1], type: match[2], body: match[3] });
78
- }
79
- return statements;
80
- }
81
-
82
- function parseManifestTests(manifestUrl, text) {
83
- const section = sectionName(manifestUrl);
84
- const tests = [];
85
-
86
- for (const statement of manifestStatements(text)) {
87
- const name = /mf:name\s+"((?:[^"\\]|\\.)*)"/m.exec(statement.body)?.[1] || statement.id;
88
-
89
- if (statement.type === 'RulesEvalTest') {
90
- const ruleset = /srt:ruleset\s+<([^>]+)>/m.exec(statement.body)?.[1];
91
- const data = /srt:data\s+<([^>]+)>/m.exec(statement.body)?.[1];
92
- const result = /mf:result\s+<([^>]+)>/m.exec(statement.body)?.[1];
93
- if (!ruleset || !data || !result) throw new Error(`Incomplete eval test ${statement.id} in ${manifestUrl}`);
94
- tests.push({
95
- section,
96
- id: statement.id,
97
- type: statement.type,
98
- name,
99
- manifestUrl,
100
- rulesetUrl: resolveHref(manifestUrl, ruleset),
101
- dataUrl: resolveHref(manifestUrl, data),
102
- resultUrl: resolveHref(manifestUrl, result),
103
- });
104
- continue;
105
- }
106
-
107
- const action = /mf:action\s+<([^>]+)>/m.exec(statement.body)?.[1];
108
- if (!action) throw new Error(`No mf:action found for ${statement.id} in ${manifestUrl}`);
109
- tests.push({
110
- section,
111
- id: statement.id,
112
- type: statement.type,
113
- name,
114
- manifestUrl,
115
- actionUrl: resolveHref(manifestUrl, action),
116
- });
117
- }
118
-
119
- return tests;
120
- }
121
-
122
- async function loadTests() {
123
- const rootText = await fetchText(rootManifestUrl);
124
- const manifestUrls = parseIncludedManifests(rootManifestUrl, rootText);
125
- const suites = await Promise.all(manifestUrls.map(async (manifestUrl) => ({
126
- manifestUrl,
127
- text: await fetchText(manifestUrl),
128
- })));
129
- return suites.flatMap(({ manifestUrl, text }) => parseManifestTests(manifestUrl, text));
130
- }
131
-
132
- function shouldPass(type) {
133
- return /Positive/.test(type) || type === 'RulesEvalTest';
134
- }
135
-
136
- async function runSyntaxOrWellformedTest(test) {
137
- const source = await fetchText(test.actionUrl);
138
- const options = { filename: test.actionUrl, baseIRI: test.actionUrl, shacl12Conformance: true };
139
-
140
- if (test.type.includes('Syntax')) {
141
- if (shouldPass(test.type)) eyeleng.parse(source, options);
142
- else assert.throws(() => eyeleng.parse(source, options), Error);
143
- return;
144
- }
145
-
146
- if (shouldPass(test.type)) eyeleng.compile(source, options);
147
- else assert.throws(() => eyeleng.compile(source, options), Error);
148
- }
149
-
150
- async function parseTurtleTriples(url) {
151
- const source = await fetchText(url);
152
- return eyeleng.parseRdfDocument(source, { filename: url, baseIRI: url }).triples;
153
- }
154
-
155
- function sortedTripleKeys(triples) {
156
- return triples.map(tripleKey).sort();
157
- }
158
-
159
- function setDiff(actual, expected) {
160
- const actualSet = new Set(actual);
161
- return expected.filter((item) => !actualSet.has(item));
162
- }
163
-
164
- async function runEvalTest(test) {
165
- const [rulesSource, dataTriples, expectedTriples] = await Promise.all([
166
- fetchText(test.rulesetUrl),
167
- parseTurtleTriples(test.dataUrl),
168
- parseTurtleTriples(test.resultUrl),
169
- ]);
170
-
171
- const compiled = eyeleng.compile(rulesSource, { filename: test.rulesetUrl, baseIRI: test.rulesetUrl, shacl12Conformance: true });
172
- const program = { ...compiled.program, data: [...compiled.program.data, ...dataTriples] };
173
- const result = eyeleng.evaluate(program, { analysis: compiled.analysis, shacl12Conformance: true });
174
-
175
- const externalInput = new Set(dataTriples.map(tripleKey));
176
- const actualTriples = result.closure.filter((triple) => !externalInput.has(tripleKey(triple)));
177
- const actual = sortedTripleKeys(actualTriples);
178
- const expected = sortedTripleKeys(expectedTriples);
179
-
180
- try {
181
- assert.deepEqual(actual, expected);
182
- } catch (err) {
183
- err.message += `\nMissing expected:\n${setDiff(actual, expected).join('\n')}\nUnexpected actual:\n${setDiff(expected, actual).join('\n')}`;
184
- throw err;
185
- }
186
- }
187
-
188
- async function runOne(test) {
189
- if (test.type === 'RulesEvalTest') return runEvalTest(test);
190
- return runSyntaxOrWellformedTest(test);
191
- }
192
-
193
25
  async function main() {
194
26
  const suiteStart = Date.now();
195
27
  info('W3C SHACL 1.2 Rules');
196
28
  console.log(`${C.dim}manifest: ${rootManifestUrl}${C.n}`);
197
29
 
198
- let tests;
30
+ let result;
199
31
  try {
200
- tests = await loadTests();
32
+ result = await runShacl12RulesManifest(rootManifestUrl, {
33
+ onProgress(item, index) {
34
+ console.log(formatShacl12RulesProgressLine(item, index, { colors: C }));
35
+ },
36
+ });
201
37
  } catch (err) {
202
- if (isLikelyNetworkError(err) && process.env.EYELENG_W3C_REQUIRED !== '1') {
203
- console.log(`${C.dim}W3C SHACL 1.2 Rules manifests not reachable here; set EYELENG_W3C_REQUIRED=1 to make this fatal.${C.n}`);
38
+ if (isLikelyNetworkError(err) && !isW3cRequired()) {
39
+ console.log(`${C.dim}W3C SHACL 1.2 Rules manifests not reachable; EYELENG_W3C_REQUIRED=0 permits this skip.${C.n}`);
204
40
  appendSummary({ section: 'W3C SHACL 1.2 Rules', passed: 1, failed: 0, total: 1, ms: Date.now() - suiteStart });
205
41
  return;
206
42
  }
207
- failLine('---', 'load W3C SHACL 1.2 Rules manifests', Date.now() - suiteStart);
208
43
  console.error(err.stack || err.message || String(err));
209
44
  appendSummary({ section: 'W3C SHACL 1.2 Rules', passed: 0, failed: 1, total: 1, ms: Date.now() - suiteStart });
210
45
  process.exitCode = 1;
211
46
  return;
212
47
  }
213
48
 
214
- const idxWidth = Math.max(3, String(Math.max(1, tests.length)).length);
215
- let passed = 0;
216
- let failed = 0;
217
- const bySection = new Map();
218
-
219
- for (let i = 0; i < tests.length; i++) {
220
- const test = tests[i];
221
- const idx = String(i + 1).padStart(idxWidth, '0');
222
- const start = Date.now();
223
- try {
224
- await runOne(test);
225
- okLine(idx, `${test.section}/${test.name}`, Date.now() - start);
226
- passed++;
227
- const section = bySection.get(test.section) || { passed: 0, failed: 0 };
228
- section.passed++;
229
- bySection.set(test.section, section);
230
- } catch (err) {
231
- failLine(idx, `${test.section}/${test.name}`, Date.now() - start);
232
- console.error(err.stack || err.message || String(err));
233
- failed++;
234
- const section = bySection.get(test.section) || { passed: 0, failed: 0 };
235
- section.failed++;
236
- bySection.set(test.section, section);
237
- }
49
+ try {
50
+ const reportPath = writeShacl12RulesEarlReport(result);
51
+ console.log(`${C.dim}EARL report: ${path.relative(path.join(__dirname, '..'), reportPath)}${C.n}`);
52
+ } catch (err) {
53
+ console.error(`Failed to write SHACL Rules EARL report: ${err.message}`);
54
+ result.counts.fail += 1;
238
55
  }
239
56
 
240
- const suiteMs = Date.now() - suiteStart;
241
- for (const [section, counts] of bySection) {
242
- const total = counts.passed + counts.failed;
243
- summaryLine(counts.failed === 0 ? 'ok' : 'fail', counts.passed, total, null, { label: section });
57
+ for (const section of result.bySection) {
58
+ summaryLine(section.failed === 0 ? 'ok' : 'fail', section.passed, section.total, null, { label: section.section });
244
59
  }
245
- summaryLine(failed === 0 ? 'ok' : 'fail', passed, tests.length, suiteMs);
60
+ summaryLine(result.counts.fail === 0 ? 'ok' : 'fail', result.counts.pass, result.counts.total, result.durationMs);
246
61
  console.log('');
247
62
 
248
- appendSummary({ section: 'W3C SHACL 1.2 Rules', passed, failed, total: tests.length, ms: suiteMs });
249
- if (failed > 0) process.exitCode = 1;
63
+ appendSummary({
64
+ section: 'W3C SHACL 1.2 Rules',
65
+ passed: result.counts.pass,
66
+ failed: result.counts.fail,
67
+ skipped: result.counts.skip,
68
+ total: result.counts.total,
69
+ ms: result.durationMs,
70
+ });
71
+ if (result.counts.fail > 0) process.exitCode = 1;
250
72
  }
251
73
 
252
74
  main().catch((err) => {
@@ -9,6 +9,7 @@ const {
9
9
  defaultW3cRdfManifestUrls,
10
10
  runW3cRdfManifests,
11
11
  formatW3cRdfProgressLine,
12
+ writeRdfEarlReport,
12
13
  } = require('../src/rdfManifest.js');
13
14
  const { parseNQuads, parseN3 } = require('../src/rdfSyntax.js');
14
15
  const { evaluateEntailmentTest, entails } = require('../src/rdfEntailment.js');
@@ -177,14 +178,16 @@ test('official W3C RDF manifests run with streaming progress when reachable', as
177
178
  },
178
179
  });
179
180
  } catch (err) {
180
- if (isLikelyNetworkError(err) && process.env.EYELENG_W3C_REQUIRED !== '1') {
181
- console.log(`${C.dim}W3C RDF manifests not reachable here; set EYELENG_W3C_REQUIRED=1 to make this fatal.${C.n}`);
181
+ if (isLikelyNetworkError(err) && process.env.EYELENG_W3C_REQUIRED === '0') {
182
+ console.log(`${C.dim}W3C RDF manifests not reachable; EYELENG_W3C_REQUIRED=0 permits this skip.${C.n}`);
182
183
  return;
183
184
  }
184
185
  throw err;
185
186
  }
186
187
  assert.equal(result.counts.fail, 0, `${result.counts.fail} W3C RDF failure(s)`);
187
188
  assert.ok(result.counts.pass > 0, 'expected at least one W3C RDF parser test to pass');
189
+ const reportPath = writeRdfEarlReport(result);
190
+ console.log(`${C.dim}EARL report: ${path.relative(path.join(__dirname, '..'), reportPath)}${C.n}`);
188
191
  summaryLine('ok', result.counts.pass, result.counts.total, result.durationMs, {
189
192
  skipped: result.counts.skip,
190
193
  label: 'W3C RDF manifests',
package/tools/w3c-rdf.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ const path = require('node:path');
4
5
  const { colors: C } = require('../test/harness.js');
5
6
  const {
6
7
  defaultW3cRdfManifestUrls,
@@ -8,13 +9,27 @@ const {
8
9
  formatW3cRdfProgressLine,
9
10
  formatW3cRdfManifestsResult,
10
11
  rdfManifestsToEarl,
12
+ writeRdfEarlReport,
13
+ defaultRdfReportPath,
11
14
  } = require('../src/rdfManifest.js');
12
15
 
16
+ function argValue(argv, name) {
17
+ const index = argv.indexOf(name);
18
+ if (index < 0) return null;
19
+ return argv[index + 1] || null;
20
+ }
21
+
13
22
  async function main(argv = process.argv.slice(2)) {
14
23
  const json = argv.includes('--json');
15
24
  const earl = argv.includes('--earl');
25
+ const noReport = argv.includes('--no-report');
16
26
  const quiet = json || earl || argv.includes('--quiet');
17
- const manifests = argv.filter((arg) => !arg.startsWith('--'));
27
+ const output = argValue(argv, '--output') || defaultRdfReportPath();
28
+ const manifests = argv.filter((arg, index) => {
29
+ if (arg.startsWith('--')) return false;
30
+ if (argv[index - 1] === '--output') return false;
31
+ return true;
32
+ });
18
33
  const resources = manifests.length ? manifests : defaultW3cRdfManifestUrls;
19
34
  const result = await runW3cRdfManifests(resources, {
20
35
  onManifestStart(resource, index, total) {
@@ -24,6 +39,10 @@ async function main(argv = process.argv.slice(2)) {
24
39
  if (!quiet) console.log(formatW3cRdfProgressLine(item, index, { colors: C }));
25
40
  },
26
41
  });
42
+ if (!noReport) {
43
+ const reportPath = writeRdfEarlReport(result, output, { assertedBy: '<https://github.com/eyereasoner/eyeleng>' });
44
+ if (!quiet) console.log(`${C.dim}EARL report: ${path.relative(path.join(__dirname, '..'), reportPath)}${C.n}`);
45
+ }
27
46
  if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
28
47
  else if (earl) process.stdout.write(`${rdfManifestsToEarl(result, { assertedBy: '<https://github.com/eyereasoner/eyeleng>' })}\n`);
29
48
  else process.stdout.write(`${formatW3cRdfManifestsResult(result, { colors: C })}\n`);
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('node:path');
5
+ const { colors: C } = require('../test/harness.js');
6
+ const {
7
+ defaultShacl12RulesManifestUrl,
8
+ runShacl12RulesManifest,
9
+ formatShacl12RulesProgressLine,
10
+ formatShacl12RulesManifestResult,
11
+ shacl12RulesManifestToEarl,
12
+ writeShacl12RulesEarlReport,
13
+ defaultReportPath,
14
+ } = require('../src/shacl12RulesManifest.js');
15
+
16
+ function argValue(argv, name) {
17
+ const index = argv.indexOf(name);
18
+ if (index < 0) return null;
19
+ return argv[index + 1] || null;
20
+ }
21
+
22
+ async function main(argv = process.argv.slice(2)) {
23
+ const json = argv.includes('--json');
24
+ const earl = argv.includes('--earl');
25
+ const noReport = argv.includes('--no-report');
26
+ const quiet = json || earl || argv.includes('--quiet');
27
+ const output = argValue(argv, '--output') || defaultReportPath();
28
+ const manifests = argv.filter((arg, index) => {
29
+ if (arg.startsWith('--')) return false;
30
+ if (argv[index - 1] === '--output') return false;
31
+ return true;
32
+ });
33
+ const manifest = manifests[0] || process.env.EYELENG_SHACL12_RULES_MANIFEST || defaultShacl12RulesManifestUrl;
34
+
35
+ const result = await runShacl12RulesManifest(manifest, {
36
+ onProgress(item, index) {
37
+ if (!quiet) console.log(formatShacl12RulesProgressLine(item, index, { colors: C }));
38
+ },
39
+ });
40
+
41
+ if (!noReport) {
42
+ const reportPath = writeShacl12RulesEarlReport(result, output);
43
+ if (!quiet) console.log(`${C.dim}EARL report: ${path.relative(path.join(__dirname, '..'), reportPath)}${C.n}`);
44
+ }
45
+
46
+ if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
47
+ else if (earl) process.stdout.write(`${shacl12RulesManifestToEarl(result)}\n`);
48
+ else process.stdout.write(`${formatShacl12RulesManifestResult(result, { colors: C })}\n`);
49
+ return result.counts.fail === 0 ? 0 : 1;
50
+ }
51
+
52
+ main().then((code) => { process.exitCode = code; }, (err) => {
53
+ console.error(err.stack || err.message || String(err));
54
+ process.exitCode = 1;
55
+ });