eyeleng 1.0.6 → 1.0.8

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.
@@ -44,4 +44,12 @@ test('playground inline scripts are syntactically valid', () => {
44
44
  assert.ok(checked > 0, 'expected at least one inline playground script');
45
45
  });
46
46
 
47
+
48
+ test('playground loads version from package.json at runtime', () => {
49
+ const html = fs.readFileSync(path.join(root, 'playground.html'), 'utf8');
50
+ assert.equal(/window\.__EYELENG_VERSION__/.test(html), false, 'playground.html must not hard-code the package version');
51
+ assert.match(html, /fetch\(new URL\(['"]package\.json['"],\s*window\.location\.href\)/);
52
+ assert.match(html, /id=["']version-label["'][^>]*>v…<\/span>/);
53
+ });
54
+
47
55
  main();
@@ -1,24 +1,18 @@
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');
6
+ const { colors: C, info, summaryLine } = require('./harness.js');
7
+ const {
8
+ defaultShacl12RulesManifestUrl,
9
+ isLikelyNetworkError,
10
+ isW3cRequired,
11
+ runShacl12RulesManifest,
12
+ formatShacl12RulesProgressLine,
13
+ } = require('../src/shacl12RulesManifest.js');
8
14
 
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
- }
15
+ const rootManifestUrl = process.env.EYELENG_SHACL12_RULES_MANIFEST || defaultShacl12RulesManifestUrl;
22
16
 
23
17
  function appendSummary(summary) {
24
18
  const file = process.env.EYELENG_TEST_SUMMARY_FILE;
@@ -27,226 +21,46 @@ function appendSummary(summary) {
27
21
  fs.appendFileSync(file, `${JSON.stringify(summary)}\n`, 'utf8');
28
22
  }
29
23
 
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
24
  async function main() {
194
25
  const suiteStart = Date.now();
195
26
  info('W3C SHACL 1.2 Rules');
196
27
  console.log(`${C.dim}manifest: ${rootManifestUrl}${C.n}`);
197
28
 
198
- let tests;
29
+ let result;
199
30
  try {
200
- tests = await loadTests();
31
+ result = await runShacl12RulesManifest(rootManifestUrl, {
32
+ onProgress(item, index) {
33
+ console.log(formatShacl12RulesProgressLine(item, index, { colors: C }));
34
+ },
35
+ });
201
36
  } 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}`);
37
+ if (isLikelyNetworkError(err) && !isW3cRequired()) {
38
+ console.log(`${C.dim}W3C SHACL 1.2 Rules manifests not reachable; EYELENG_W3C_REQUIRED=0 permits this skip.${C.n}`);
204
39
  appendSummary({ section: 'W3C SHACL 1.2 Rules', passed: 1, failed: 0, total: 1, ms: Date.now() - suiteStart });
205
40
  return;
206
41
  }
207
- failLine('---', 'load W3C SHACL 1.2 Rules manifests', Date.now() - suiteStart);
208
42
  console.error(err.stack || err.message || String(err));
209
43
  appendSummary({ section: 'W3C SHACL 1.2 Rules', passed: 0, failed: 1, total: 1, ms: Date.now() - suiteStart });
210
44
  process.exitCode = 1;
211
45
  return;
212
46
  }
213
47
 
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
- }
238
- }
239
48
 
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 });
49
+ for (const section of result.bySection) {
50
+ summaryLine(section.failed === 0 ? 'ok' : 'fail', section.passed, section.total, null, { label: section.section });
244
51
  }
245
- summaryLine(failed === 0 ? 'ok' : 'fail', passed, tests.length, suiteMs);
52
+ summaryLine(result.counts.fail === 0 ? 'ok' : 'fail', result.counts.pass, result.counts.total, result.durationMs);
246
53
  console.log('');
247
54
 
248
- appendSummary({ section: 'W3C SHACL 1.2 Rules', passed, failed, total: tests.length, ms: suiteMs });
249
- if (failed > 0) process.exitCode = 1;
55
+ appendSummary({
56
+ section: 'W3C SHACL 1.2 Rules',
57
+ passed: result.counts.pass,
58
+ failed: result.counts.fail,
59
+ skipped: result.counts.skip,
60
+ total: result.counts.total,
61
+ ms: result.durationMs,
62
+ });
63
+ if (result.counts.fail > 0) process.exitCode = 1;
250
64
  }
251
65
 
252
66
  main().catch((err) => {
@@ -177,8 +177,8 @@ test('official W3C RDF manifests run with streaming progress when reachable', as
177
177
  },
178
178
  });
179
179
  } 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}`);
180
+ if (isLikelyNetworkError(err) && process.env.EYELENG_W3C_REQUIRED === '0') {
181
+ console.log(`${C.dim}W3C RDF manifests not reachable; EYELENG_W3C_REQUIRED=0 permits this skip.${C.n}`);
182
182
  return;
183
183
  }
184
184
  throw err;
package/tools/bundle.js CHANGED
@@ -57,22 +57,6 @@ function js(value) {
57
57
  }
58
58
 
59
59
 
60
- function packageVersion() {
61
- const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
62
- if (!packageJson.version) throw new Error('package.json is missing a version');
63
- return packageJson.version;
64
- }
65
-
66
- function syncPlaygroundVersion() {
67
- const version = packageVersion();
68
- const outPath = path.join(root, playgroundOutput);
69
- const html = fs.readFileSync(outPath, 'utf8');
70
- const pattern = /(window\.__EYELENG_VERSION__\s*=\s*)["'][^"']*["']\s*;/;
71
- if (!pattern.test(html)) throw new Error('Could not find window.__EYELENG_VERSION__ in playground.html');
72
- const next = html.replace(pattern, `$1${js(version)};`);
73
- fs.writeFileSync(outPath, next, 'utf8');
74
- console.log(`wrote ${playgroundOutput} version ${version}`);
75
- }
76
60
 
77
61
  function ensureParentDir(filename) {
78
62
  fs.mkdirSync(path.dirname(filename), { recursive: true });
@@ -172,4 +156,3 @@ function indent(source, spaces) {
172
156
 
173
157
  buildCli();
174
158
  buildBrowser();
175
- syncPlaygroundVersion();
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
+ });