1 | #!/usr/bin/env node
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | const ttl = {
|
7 |
|
8 | get read() {
|
9 |
|
10 | delete ttl.read;
|
11 | return ttl.read = require('@graphy-dev/content.ttl.read');
|
12 | },
|
13 |
|
14 | get scan() {
|
15 |
|
16 | delete ttl.scan;
|
17 | return ttl.scan = require('@graphy-dev/content.ttl.scan');
|
18 | },
|
19 |
|
20 | get write() {
|
21 |
|
22 | delete ttl.write;
|
23 | return ttl.write = require('@graphy-dev/content.ttl.write');
|
24 | },
|
25 | };
|
26 |
|
27 | const trig = {
|
28 |
|
29 | get read() {
|
30 |
|
31 | delete trig.read;
|
32 | return trig.read = require('@graphy-dev/content.trig.read');
|
33 | },
|
34 |
|
35 | get scan() {
|
36 |
|
37 | delete trig.scan;
|
38 | return trig.scan = require('@graphy-dev/content.trig.scan');
|
39 | },
|
40 |
|
41 | get write() {
|
42 |
|
43 | delete trig.write;
|
44 | return trig.write = require('@graphy-dev/content.trig.write');
|
45 | },
|
46 | };
|
47 |
|
48 | const nt = {
|
49 |
|
50 | get read() {
|
51 |
|
52 | delete nt.read;
|
53 | return nt.read = require('@graphy-dev/content.nt.read');
|
54 | },
|
55 |
|
56 | get scan() {
|
57 |
|
58 | delete nt.scan;
|
59 | return nt.scan = require('@graphy-dev/content.nt.scan');
|
60 | },
|
61 |
|
62 | get write() {
|
63 |
|
64 | delete nt.write;
|
65 | return nt.write = require('@graphy-dev/content.nt.write');
|
66 | },
|
67 | };
|
68 |
|
69 | const nq = {
|
70 |
|
71 | get read() {
|
72 |
|
73 | delete nq.read;
|
74 | return nq.read = require('@graphy-dev/content.nq.read');
|
75 | },
|
76 |
|
77 | get scan() {
|
78 |
|
79 | delete nq.scan;
|
80 | return nq.scan = require('@graphy-dev/content.nq.scan');
|
81 | },
|
82 |
|
83 | get write() {
|
84 |
|
85 | delete nq.write;
|
86 | return nq.write = require('@graphy-dev/content.nq.write');
|
87 | },
|
88 | };
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | const H_CONTENT_MIMES = {
|
103 | 'text/turtle': ttl,
|
104 | 'application/trig': trig,
|
105 | 'application/n-triples': nt,
|
106 | 'application/n-quads': nq,
|
107 |
|
108 | };
|
109 |
|
110 | const H_CONTENT_TAGS = {
|
111 | ttl,
|
112 | trig,
|
113 | nt,
|
114 | nq,
|
115 |
|
116 | };
|
117 |
|
118 |
|
119 |
|
120 | const R_CONTENT_TYPE = /^((?:application|text)\/[^\0-\x20()<>@,;:\\"\/[\]?.=]+)(;.+)*$/i;
|
121 |
|
122 | const graphy = module.exports = Object.assign({
|
123 |
|
124 | content: Object.assign((s_query) => {
|
125 | let m_content_type = R_CONTENT_TYPE.exec(s_query);
|
126 | if(!m_content_type) throw new Error(`invalid content-type string: "${s_query}"`);
|
127 | let [, s_content_type, s_parameters] = m_content_type;
|
128 | let s_content_type_normal = s_content_type.toLowerCase();
|
129 |
|
130 | if(s_content_type_normal in H_CONTENT_TAGS) {
|
131 | return H_CONTENT_TAGS[s_content_type_normal];
|
132 | }
|
133 | else if(s_content_type_normal in H_CONTENT_MIMES) {
|
134 | return H_CONTENT_MIMES[s_content_type_normal];
|
135 | }
|
136 | else {
|
137 | throw new Error(`no content handlers matched query for "${s_content_type_normal}"`);
|
138 | }
|
139 | }, {
|
140 | ttl,
|
141 | trig,
|
142 | nt,
|
143 | nq,
|
144 |
|
145 | }),
|
146 |
|
147 | util: {
|
148 | dataset: {
|
149 | get tree() {
|
150 |
|
151 | delete graphy.util.dataset.tree;
|
152 | return (graphy.util.dataset.tree = require('@graphy-dev/util.dataset.tree'));
|
153 | },
|
154 | },
|
155 | },
|
156 |
|
157 |
|
158 | }, require('@graphy-dev/core.data.factory'));
|
159 |
|
160 |
|
161 |
|
162 | if('undefined' !== typeof window) window.graphy = graphy;
|
163 |
|
164 |
|
165 | if(module === require.main) {
|
166 | const fs =require('fs');
|
167 | const mk_yargs = require('yargs/yargs');
|
168 |
|
169 | const exit = (s_exit) => {
|
170 | console.error(s_exit);
|
171 | process.exit(1);
|
172 | };
|
173 |
|
174 | const command = s_command => mk_yargs()
|
175 | .strict()
|
176 | .usage(`Usage: $0 ${s_command} [OPTIONS] [--pipe COMMAND]`);
|
177 |
|
178 | const reader = f_reader => (a_args, g_context) => new Promise((fk_resolve) => {
|
179 | let g_argv = command(g_context.command)
|
180 | .boolean('v')
|
181 | .alias('v', 'validate')
|
182 | .describe('v', 'validate all tokens within the RDF document')
|
183 | .string('b')
|
184 | .alias('b', ['base', 'base-uri'])
|
185 | .describe('b', 'set a base URI on the document')
|
186 | .help()
|
187 | .version(false)
|
188 | .parse(a_args);
|
189 |
|
190 | let gc_read = {
|
191 | validate: g_argv.validate || false,
|
192 | };
|
193 |
|
194 | if(g_argv['base-uri']) {
|
195 | gc_read.baseUri = g_argv['base-uri'];
|
196 | }
|
197 |
|
198 | fk_resolve(g_context.inputs.map((ds_input) => {
|
199 | let ds_reader = f_reader({...gc_read});
|
200 | return ds_input.pipe(ds_reader);
|
201 | }));
|
202 | });
|
203 |
|
204 | const writer = f_writer => (a_args, g_context) => {
|
205 | let g_argv = command(g_context.command)
|
206 | .help()
|
207 | .parse(a_args);
|
208 |
|
209 | let gc_write = {};
|
210 |
|
211 | return g_context.inputs.map((ds_input) => {
|
212 | let ds_writer = f_writer({...gc_write});
|
213 | return ds_input.pipe(ds_writer);
|
214 | });
|
215 | };
|
216 |
|
217 |
|
218 | let h_commands = {
|
219 | 'content.nt.read': reader(graphy.content.nt.read),
|
220 | 'content.nq.read': reader(graphy.content.nq.read),
|
221 | 'content.ttl.read': reader(graphy.content.ttl.read),
|
222 | 'content.trig.read': reader(graphy.content.trig.read),
|
223 |
|
224 | 'content.nt.write': writer(graphy.content.nt.write),
|
225 | 'content.nq.write': writer(graphy.content.nq.write),
|
226 | 'content.ttl.write': writer(graphy.content.ttl.write),
|
227 | 'content.trig.write': writer(graphy.content.trig.write),
|
228 |
|
229 | 'util.dataset.tree': async(a_args, g_context) => {
|
230 | const dataset_tree = graphy.util.dataset.tree;
|
231 |
|
232 | let a_dual_input_keys = ['u', 'i', 'd', 'm'];
|
233 |
|
234 | let s_group_multi_input = 'With multiple inputs:';
|
235 | let s_group_dual_input = 'With exactly two inputs:';
|
236 |
|
237 | let g_argv = command(g_context.command)
|
238 | .options({
|
239 | u: {
|
240 | type: 'boolean',
|
241 | alias: 'union',
|
242 | group: s_group_multi_input,
|
243 | describe: 'perform the union of multiple inputs',
|
244 | conflicts: a_dual_input_keys.filter(s => 'u' !== s),
|
245 | },
|
246 | i: {
|
247 | type: 'boolean',
|
248 | alias: ['intersect', 'intersection'],
|
249 | group: s_group_multi_input,
|
250 | describe: 'perform the intersection of multiple inputs',
|
251 | conflicts: a_dual_input_keys.filter(s => 'i' !== s),
|
252 | },
|
253 | d: {
|
254 | type: 'boolean',
|
255 | alias: ['diff', 'difference'],
|
256 | group: s_group_dual_input,
|
257 | describe: 'perform a difference between two inputs',
|
258 | conflicts: a_dual_input_keys.filter(s => 'd' !== s),
|
259 | },
|
260 | m: {
|
261 | type: 'boolean',
|
262 | alias: ['minus', 'subtract', 'subtraction'],
|
263 | group: s_group_dual_input,
|
264 | describe: 'perform a subtraction by removing input-B from input-A',
|
265 | conflicts: a_dual_input_keys.filter(s => 'm' !== s),
|
266 | },
|
267 | })
|
268 | .help()
|
269 | .version(false)
|
270 | .parse(a_args);
|
271 |
|
272 |
|
273 | let a_inputs = g_context.inputs;
|
274 | let n_inputs = a_inputs.length;
|
275 |
|
276 |
|
277 | if(g_argv.union || g_argv.intersection) {
|
278 | let s_operation = g_argv.union
|
279 | ? 'union'
|
280 | : 'intersection';
|
281 |
|
282 |
|
283 | if(n_inputs < 2) return a_inputs;
|
284 |
|
285 |
|
286 | let a_trees = a_inputs.map(() => dataset_tree());
|
287 |
|
288 |
|
289 | let k_tree_out = a_trees[0];
|
290 |
|
291 |
|
292 | for(let i_input=0; i_input<n_inputs; i_input++) {
|
293 | let k_tree_b = a_trees[i_input];
|
294 |
|
295 |
|
296 | a_inputs[i_input].pipe(k_tree_b);
|
297 |
|
298 |
|
299 | await k_tree_b.until('finish');
|
300 |
|
301 |
|
302 | if(i_input) {
|
303 |
|
304 | k_tree_out = k_tree_out[s_operation](k_tree_b);
|
305 | }
|
306 | }
|
307 |
|
308 |
|
309 | return [k_tree_out];
|
310 | }
|
311 |
|
312 | else if(g_argv.difference || g_argv.subtraction) {
|
313 | let s_operation = g_argv.difference
|
314 | ? 'difference'
|
315 | : 'minus';
|
316 |
|
317 |
|
318 | if(2 !== n_inputs) {
|
319 | exit(`dual input operation expects two inputs but found ${n_inputs}`);
|
320 | }
|
321 |
|
322 |
|
323 | return new Promise((fk_resolve) => {
|
324 | let operate = () => [k_tree_a[s_operation](k_tree_b)];
|
325 |
|
326 |
|
327 | let k_tree_a = dataset_tree();
|
328 | let b_finished_a = false;
|
329 | k_tree_a.on('finish', () => {
|
330 | b_finished_a = true;
|
331 | if(b_finished_b) fk_resolve(operate());
|
332 | });
|
333 |
|
334 |
|
335 | let k_tree_b = dataset_tree();
|
336 | let b_finished_b = false;
|
337 | k_tree_b.on('finish', () => {
|
338 | b_finished_b = true;
|
339 | if(b_finished_a) fk_resolve(operate());
|
340 | });
|
341 |
|
342 |
|
343 | let [ds_input_a, ds_input_b] = a_inputs;
|
344 |
|
345 |
|
346 | ds_input_a.pipe(k_tree_a);
|
347 | ds_input_b.pipe(k_tree_b);
|
348 | });
|
349 | }
|
350 |
|
351 | else {
|
352 | return g_context.inputs.map(ds_input => ds_input.pipe(dataset_tree()));
|
353 | }
|
354 | },
|
355 | };
|
356 |
|
357 | let a_argv = process.argv.slice(2);
|
358 | let n_args = a_argv.length;
|
359 |
|
360 |
|
361 | let a_inputs = [];
|
362 | {
|
363 | for(let i_argv=0; i_argv<n_args; i_argv++) {
|
364 | let s_arg = a_argv[i_argv];
|
365 |
|
366 |
|
367 | if(s_arg.startsWith('--input')) {
|
368 | let m_input = /^--input=(.+)$/.exec(s_arg);
|
369 | if(!m_input) {
|
370 | exit(`invalid input argument format: ${s_arg}`);
|
371 | }
|
372 | a_argv.shift();
|
373 | i_argv -= 1;
|
374 | n_args -= 1;
|
375 | a_inputs.push(fs.createReadStream(m_input[1]));
|
376 | continue;
|
377 | }
|
378 |
|
379 | break;
|
380 | }
|
381 | }
|
382 |
|
383 |
|
384 | let a_pipeline = [];
|
385 | {
|
386 | let a_series = [a_argv[0]];
|
387 |
|
388 | for(let i_argv=1; i_argv<n_args; i_argv++) {
|
389 | let s_arg = a_argv[i_argv];
|
390 |
|
391 | if('--pipe' === s_arg) {
|
392 | a_pipeline.push(a_series);
|
393 | if(i_argv === n_args) {
|
394 | exit(`was expecting pipe destination after --pipe: ${a_argv}`);
|
395 | }
|
396 | a_series = [];
|
397 | continue;
|
398 | }
|
399 |
|
400 | a_series.push(s_arg);
|
401 | }
|
402 |
|
403 | a_pipeline.push(a_series);
|
404 | }
|
405 |
|
406 |
|
407 | {
|
408 | let a_series = a_pipeline[a_pipeline.length-1];
|
409 | let n_series = a_series.length;
|
410 |
|
411 |
|
412 | for(let i_end=n_series-1; i_end>=0; i_end--) {
|
413 |
|
414 | if(a_series[i_end].startsWith('/')) {
|
415 |
|
416 | a_inputs.push(fs.createReadStream(a_series.pop()));
|
417 |
|
418 |
|
419 | continue;
|
420 | }
|
421 |
|
422 |
|
423 | break;
|
424 | }
|
425 |
|
426 |
|
427 | if(!a_series.length) a_pipeline.pop();
|
428 | }
|
429 |
|
430 |
|
431 | if(!a_pipeline.length) {
|
432 | exit('no commands given');
|
433 | }
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 | (async() => {
|
478 |
|
479 | let a_prev = a_inputs.length? a_inputs: [process.stdin];
|
480 |
|
481 |
|
482 | for(let a_series of a_pipeline) {
|
483 |
|
484 | let s_command = a_series[0];
|
485 |
|
486 |
|
487 | if(!(s_command in h_commands)) {
|
488 | exit(`no such command '${s_command}'`);
|
489 | }
|
490 |
|
491 | try {
|
492 |
|
493 | let a_curr = await h_commands[s_command](a_series.slice(1), {
|
494 | command: s_command,
|
495 | inputs: a_prev,
|
496 | });
|
497 |
|
498 |
|
499 | a_prev = a_curr;
|
500 | }
|
501 | catch(e_parse) {
|
502 |
|
503 | return;
|
504 | }
|
505 | }
|
506 |
|
507 |
|
508 | if(1 !== a_prev.length) {
|
509 | exit(`expected a single output stream but last command produces ${a_prev.length} streams`);
|
510 | }
|
511 |
|
512 |
|
513 | a_prev[0].pipe(process.stdout);
|
514 | })();
|
515 | }
|