UNPKG

27.6 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3
4const factory = require('@graphy/core.data.factory');
5
6const gobble = (s_text, s_indent='') => {
7 let m_pad = /^(\s+)/.exec(s_text.replace(/^([ \t]*\n)/, ''));
8 if(m_pad) {
9 return s_indent+s_text.replace(new RegExp(`\\n${m_pad[1]}`, 'g'), '\n'+s_indent.trim()).trim();
10 }
11 else {
12 return s_indent+s_text.trim();
13 }
14};
15
16
17
18 // ttl package
19const ttl = {
20 // read ttl output
21 get read() {
22 // memoize
23 delete ttl.read;
24 return ttl.read = require('@graphy/content.ttl.read'); // eslint-disable-line global-require
25 },
26 // scan ttl output
27 get scan() {
28 // memoize
29 delete ttl.scan;
30 return ttl.scan = require('@graphy/content.ttl.scan'); // eslint-disable-line global-require
31 },
32 // write ttl input
33 get write() {
34 // memoize
35 delete ttl.write;
36 return ttl.write = require('@graphy/content.ttl.write'); // eslint-disable-line global-require
37 },
38};
39 // trig package
40const trig = {
41 // read trig output
42 get read() {
43 // memoize
44 delete trig.read;
45 return trig.read = require('@graphy/content.trig.read'); // eslint-disable-line global-require
46 },
47 // scan trig output
48 get scan() {
49 // memoize
50 delete trig.scan;
51 return trig.scan = require('@graphy/content.trig.scan'); // eslint-disable-line global-require
52 },
53 // write trig input
54 get write() {
55 // memoize
56 delete trig.write;
57 return trig.write = require('@graphy/content.trig.write'); // eslint-disable-line global-require
58 },
59};
60 // nt package
61const nt = {
62 // read nt output
63 get read() {
64 // memoize
65 delete nt.read;
66 return nt.read = require('@graphy/content.nt.read'); // eslint-disable-line global-require
67 },
68 // scan nt output
69 get scan() {
70 // memoize
71 delete nt.scan;
72 return nt.scan = require('@graphy/content.nt.scan'); // eslint-disable-line global-require
73 },
74 // write nt input
75 get write() {
76 // memoize
77 delete nt.write;
78 return nt.write = require('@graphy/content.nt.write'); // eslint-disable-line global-require
79 },
80};
81 // nq package
82const nq = {
83 // read nq output
84 get read() {
85 // memoize
86 delete nq.read;
87 return nq.read = require('@graphy/content.nq.read'); // eslint-disable-line global-require
88 },
89 // scan nq output
90 get scan() {
91 // memoize
92 delete nq.scan;
93 return nq.scan = require('@graphy/content.nq.scan'); // eslint-disable-line global-require
94 },
95 // write nq input
96 get write() {
97 // memoize
98 delete nq.write;
99 return nq.write = require('@graphy/content.nq.write'); // eslint-disable-line global-require
100 },
101};
102
103
104// // SPARQL Results package
105// const sparql_results = {
106// // deserialize sparql_results input
107// get deserializer() {
108// // memoize
109// delete sparql_results.deserializer;
110// return (sparql_results.deserializer = require('../sparql-results/deserializer.js'));
111// },
112// };
113
114
115const H_CONTENT_MIMES = {
116 'text/turtle': ttl,
117 'application/trig': trig,
118 'application/n-triples': nt,
119 'application/n-quads': nq,
120 // 'application/sparql-results+json': sparql_results,
121};
122
123const H_CONTENT_TAGS = {
124 ttl,
125 trig,
126 nt,
127 nq,
128 // 'application/sparql-results+json': sparql_results,
129};
130
131
132
133const R_CONTENT_TYPE = /^((?:application|text)\/[^\0-\x20()<>@,;:\\"\/[\]?.=]+)(;.+)*$/i;
134
135const graphy = module.exports = Object.assign({
136
137 content: Object.assign((s_query) => {
138 if(s_query in H_CONTENT_TAGS) {
139 return H_CONTENT_TAGS[s_query];
140 }
141
142 let m_content_type = R_CONTENT_TYPE.exec(s_query);
143 if(!m_content_type) throw new Error(`invalid content-type string: "${s_query}"`);
144 let [, s_content_type, s_parameters] = m_content_type;
145 let s_content_type_normal = s_content_type.toLowerCase();
146
147 if(s_content_type_normal in H_CONTENT_MIMES) {
148 return H_CONTENT_MIMES[s_content_type_normal];
149 }
150 else {
151 throw new Error(`no content handlers matched query for "${s_content_type_normal}"`);
152 }
153 }, {
154 ttl,
155 trig,
156 nt,
157 nq,
158
159 }),
160
161 core: {
162 data: {
163 get factory() {
164 // memoize
165 delete graphy.core.data.factory;
166 return (graphy.core.data.factory = require('@graphy/core.data.factory'));
167 },
168 },
169 },
170
171 get 'core.data.factory'() {
172 delete graphy['core.data.factory'];
173 return (graphy['core.data.factory'] = require('@graphy/core.data.factory'));
174 },
175
176 get 'util.dataset.tree'() {
177 delete graphy['util.dataset.tree'];
178 return (graphy['util.dataset.tree'] = require('@graphy/util.dataset.tree'));
179 },
180
181 util: {
182 dataset: {
183 get tree() {
184 // memoize
185 delete graphy.util.dataset.tree;
186 return (graphy.util.dataset.tree = require('@graphy/util.dataset.tree'));
187 },
188 },
189 },
190
191
192 get 'content.ttl.read'() {
193 // memoize
194 delete graphy['content.ttl.read'];
195 return graphy['content.ttl.read'] = require('@graphy/content.ttl.read'); // eslint-disable-line global-require
196 },
197
198 get 'content.ttl.write'() {
199 // memoize
200 delete graphy['content.ttl.write'];
201 return graphy['content.ttl.write'] = require('@graphy/content.ttl.write'); // eslint-disable-line global-require
202 },
203
204 get 'content.trig.read'() {
205 // memoize
206 delete graphy['content.trig.read'];
207 return graphy['content.trig.read'] = require('@graphy/content.trig.read'); // eslint-disable-line global-require
208 },
209
210 get 'content.trig.write'() {
211 // memoize
212 delete graphy['content.trig.write'];
213 return graphy['content.trig.write'] = require('@graphy/content.trig.write'); // eslint-disable-line global-require
214 },
215
216 get 'content.nt.read'() {
217 // memoize
218 delete graphy['content.nt.read'];
219 return graphy['content.nt.read'] = require('@graphy/content.nt.read'); // eslint-disable-line global-require
220 },
221
222 get 'content.nt.write'() {
223 // memoize
224 delete graphy['content.nt.write'];
225 return graphy['content.nt.write'] = require('@graphy/content.nt.write'); // eslint-disable-line global-require
226 },
227
228 get 'content.nq.read'() {
229 // memoize
230 delete graphy['content.nq.read'];
231 return graphy['content.nq.read'] = require('@graphy/content.nq.read'); // eslint-disable-line global-require
232 },
233
234 get 'content.nq.write'() {
235 // memoize
236 delete graphy['content.nq.write'];
237 return graphy['content.nq.write'] = require('@graphy/content.nq.write'); // eslint-disable-line global-require
238 },
239
240
241
242}, factory);
243
244
245// export graphy to window object if in main thread of browser
246if('undefined' !== typeof window) window.graphy = graphy;
247
248// cli
249if(module === require.main) {
250 const fs =require('fs');
251 const path = require('path');
252 const mk_yargs = require('yargs/yargs');
253 const stream = require('@graphy/core.iso.stream');
254
255 class answer_source extends require('stream').Readable {
256 constructor(w_datum) {
257 super({
258 objectMode: true,
259 });
260
261 this.datum = w_datum;
262 }
263
264 // intercept pipe
265 pipe(ds_dst) {
266 // string out
267 if(!ds_dst._writableState.objectMode) {
268 // change read mode; push as JSON
269 this._read = () => {
270 this.push(JSON.stringify(this.datum)+'\n', 'utf8');
271 this.push(null);
272 };
273 }
274
275 // forward to super
276 return super.pipe(ds_dst);
277 }
278
279 // push object
280 _read() {
281 this.push(this.datum);
282 this.push(this.null);
283 }
284 }
285
286 const exit = (s_exit) => {
287 console.error(s_exit);
288 process.exit(1);
289 };
290
291 const command = s_command => mk_yargs()
292 .strict()
293 .usage(`Usage: $0 ${s_command} [OPTIONS] [--pipe COMMAND]`);
294
295 const reader = f_reader => (a_args, g_context) => new Promise((fk_resolve) => {
296 let g_argv = command(g_context.command)
297 .options({
298 v: {
299 type: 'boolean',
300 alias: ['validate'],
301 describe: 'validate all tokens within the RDF document',
302 },
303 b: {
304 type: 'string',
305 alias: ['base', 'base-uri'],
306 describe: 'set a base URI on the document',
307 },
308 s: {
309 type: 'string',
310 alias: ['subject'],
311 describe: 'filter quads by only allowing those that match the given subject (must be a concise-term string)',
312 conflicts: ['S'],
313 },
314 p: {
315 type: 'string',
316 alias: ['predicate'],
317 describe: 'filter quads by only allowing those that match the given predicate (must be a concise-term string)',
318 conflicts: ['P'],
319 },
320 o: {
321 type: 'string',
322 alias: ['object'],
323 describe: 'filter quads by only allowing those that match the given object (must be a concise-term string)',
324 conflicts: ['O'],
325 },
326 g: {
327 type: 'string',
328 alias: ['graph'],
329 describe: 'filter quads by only allowing those that match the given graph (must be a concise-term string)',
330 conflicts: ['G'],
331 },
332 S: {
333 type: 'array',
334 alias: ['not-subject'],
335 describe: 'filter quads by allowing any that *do not match* the given subject(s) (must be concise-term string(s))',
336 conflicts: ['s'],
337 },
338 P: {
339 type: 'array',
340 alias: ['not-predicate'],
341 describe: 'filter quads by allowing any that *do not match* the given predicate(s) (must be a concise-term string(s))',
342 conflicts: ['p'],
343 },
344 O: {
345 type: 'array',
346 alias: ['not-object'],
347 describe: 'filter quads by allowing any that *do not match* the given object(s) (must be a concise-term string(s))',
348 conflicts: ['o'],
349 },
350 G: {
351 type: 'array',
352 alias: ['not-graph'],
353 describe: 'filter quads by allowing any that *do not match* the given graph(s) (must be a concise-term string(s))',
354 conflicts: ['g'],
355 },
356 })
357 .help()
358 .version(false)
359 .parse(a_args);
360
361 let gc_read = {
362 validate: g_argv.validate || false,
363 };
364
365 if(g_argv['base-uri']) {
366 gc_read.baseUri = g_argv['base-uri'];
367 }
368
369 fk_resolve(g_context.inputs.map((ds_input) => {
370 let ds_reader = ds_input.pipe(f_reader({
371 ...gc_read,
372 error(e_read) {
373 g_context.failure(e_read);
374 },
375 }));
376
377 // filters
378 if(g_argv.subject || g_argv.predicate || g_argv.object || g_argv.graph
379 || g_argv['not-subject'] || g_argv['not-predicate'] || g_argv['not-object'] || g_argv['not-graph']) {
380 let {
381 subject: sc1_subject=null,
382 predicate: sc1_predicate=null,
383 object: sc1_object=null,
384 graph: sc1_graph=null,
385 'not-subject': a_not_subjects_src=null,
386 'not-predicate': a_not_predicates_src=null,
387 'not-object': a_not_objects_src=null,
388 'not-graph': a_not_graphs_src=null,
389 } = g_argv;
390
391 // create sv1 test strings
392 let sv1_subject = null;
393 let sv1_predicate = null;
394 let sv1_object = null;
395 let sv1_graph = null;
396
397 // create not filter arrays
398 let a_not_subjects = null;
399 let a_not_predicates = null;
400 let a_not_objects = null;
401 let a_not_graphs = null;
402
403 // prefix mappings
404 let h_prefixes = {};
405
406 // graph optimzation
407 let b_skip_graph = false;
408
409 // skip graphs that do not match filter
410 let f_enter = (yt_graph) => {
411 b_skip_graph = (sv1_graph !== yt_graph.concise());
412 };
413
414 // only skip triples if filter is not the default graph
415 let f_exit = () => {
416 b_skip_graph = ('*' !== sv1_graph);
417 };
418
419 // skip graphs that match not filter
420 let f_enter_not = (yt_graph) => {
421 b_skip_graph = a_not_graphs.includes(yt_graph.concise());
422 };
423
424 // only skip triples if not filter is the default graph
425 let f_exit_not = () => {
426 b_skip_graph = a_not_graphs.includes('*');
427 };
428
429 // when prefix mappings change, populate filter strings
430 let f_update_filters = () => {
431 // filter subject
432 if(sc1_subject) {
433 try {
434 sv1_subject = factory.c1_node(sc1_subject, h_prefixes).concise();
435 }
436 catch(e_convert) {
437 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
438 sv1_subject = '`void';
439 }
440 }
441 // filter not subject
442 else if(a_not_subjects_src) {
443 a_not_subjects = a_not_subjects_src.map((sc1_not_subject) => {
444 try {
445 return factory.c1_node(sc1_not_subject, h_prefixes).concise();
446 }
447 catch(e_convert) {
448 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
449 return null;
450 }
451 });
452 }
453
454 // filter predicate
455 if(sc1_predicate) {
456 try {
457 sv1_predicate = factory.c1_named_node(sc1_predicate, h_prefixes).concise();
458 }
459 catch(e_convert) {
460 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
461 sv1_predicate = '`void';
462 }
463 }
464 // filter not predicate
465 else if(a_not_predicates_src) {
466 a_not_predicates = a_not_predicates_src.map((sc1_not_predicate) => {
467 try {
468 return factory.c1_node(sc1_not_predicate, h_prefixes).concise();
469 }
470 catch(e_convert) {
471 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
472 return null;
473 }
474 });
475 }
476
477 // filter object
478 if(sc1_object) {
479 try {
480 sv1_object = factory.c1(sc1_object, h_prefixes).concise();
481 }
482 catch(e_convert) {
483 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
484 sv1_object = '`void';
485 }
486 }
487 // filter not object
488 else if(a_not_objects) {
489 a_not_objects = a_not_objects_src.map((sc1_not_object) => {
490 try {
491 return factory.c1(sc1_not_object, h_prefixes).concise();
492 }
493 catch(e_convert) {
494 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
495 return null;
496 }
497 });
498 }
499
500 // filter graph
501 if(sc1_graph) {
502 // listeners already set, remove them
503 if(sv1_graph) {
504 ds_reader.removeListener('enter', f_enter);
505 ds_reader.removeListener('exit', f_exit);
506 }
507
508 try {
509 sv1_graph = factory.c1_node(sc1_graph, h_prefixes).concise();
510 }
511 catch(e_convert) {
512 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
513 sv1_graph = '`void';
514 }
515
516 // setup graph filter
517 ds_reader
518 .on('enter', f_enter)
519 .on('exit', f_exit);
520 }
521 // filter not graph
522 else if(a_not_graphs_src) {
523 // listeners might already set, remove them
524 if(a_not_graphs) {
525 ds_reader.removeListener('enter', f_enter_not);
526 ds_reader.removeListener('exit', f_exit_not);
527 }
528
529 a_not_graphs = a_not_graphs_src.map((sc1_not_graph) => {
530 try {
531 return factory.c1_node(sc1_not_graph, h_prefixes).concise();
532 }
533 catch(e_convert) {
534 if(!/prefix not defined/i.test(e_convert.message)) throw e_convert;
535 return null;
536 }
537 });
538
539 // setup graph filter
540 ds_reader
541 .on('enter', f_enter_not)
542 .on('exit', f_exit_not);
543 }
544 };
545
546 // listen for prefix events
547 ds_reader.on('prefix', (s_prefix_id, p_prefix_iri) => {
548 // update hash
549 h_prefixes[s_prefix_id] = p_prefix_iri;
550
551 // update filters
552 f_update_filters();
553 });
554
555 // create filter transform
556 let ds_filter = new (class extends stream.Transform {
557 constructor() {
558 super({
559 objectMode: true,
560 });
561 }
562
563 // eslint-disable-next-line class-methods-use-this
564 _transform(y_quad, s_encoding, fk_transform) {
565 // skip graph
566 if(b_skip_graph) return fk_transform();
567
568 // apply filter
569 if(sv1_subject && sv1_subject !== y_quad.subject.concise()) return fk_transform();
570 else if(sv1_predicate && sv1_predicate !== y_quad.predicate.concise()) return fk_transform();
571 else if(sv1_object && sv1_object !== y_quad.object.concise()) return fk_transform();
572 else if(sv1_graph && sv1_graph !== y_quad.graph.concise()) return fk_transform();
573
574 // apply not filter
575 if(a_not_subjects && a_not_subjects.includes(y_quad.subject.concise())) return fk_transform();
576 else if(a_not_predicates && a_not_predicates.includes(y_quad.predicate.concise())) return fk_transform();
577 else if(a_not_objects && a_not_objects.includes(y_quad.object.concise())) return fk_transform();
578 else if(a_not_graphs && a_not_graphs.includes(y_quad.graph.concise())) return fk_transform();
579
580 // quad passed filter
581 fk_transform(null, y_quad);
582 }
583
584 // intercept pipe
585 pipe(ds_out) {
586 let ds_dst = ds_out;
587
588 // non-object mode
589 if(!ds_dst._writableState.objectMode) {
590 // transform to JSON
591 ds_out = stream.quads_to_json();
592 }
593 // yet object mode and graphy writable
594 else if(ds_out.isGraphyWritable) {
595 // transform to writable data events
596 ds_out = stream.quads_to_writable();
597 }
598
599 // interim stream created
600 if(ds_out !== ds_dst) {
601 // forward output to super
602 super.pipe(ds_out);
603
604 // pipe outpu to destination
605 return ds_out.pipe(ds_dst);
606 }
607 // forward as-is to super
608 else {
609 return super.pipe(ds_dst);
610 }
611 }
612 })();
613
614 // pipe thru filter
615 return ds_reader.pipe(ds_filter);
616 }
617 // no filter
618 else {
619 return ds_reader;
620 }
621 }));
622 });
623
624 const writer = f_writer => (a_args, g_context) => {
625 let g_argv = command(g_context.command)
626 .help()
627 .parse(a_args);
628
629 let gc_write = {};
630
631 return g_context.inputs.map((ds_input) => {
632 let ds_writer = f_writer({
633 ...gc_write,
634 error(e_write) {
635 g_context.failure(e_write);
636 },
637 });
638 return ds_input.pipe(ds_writer);
639 });
640 };
641
642 // commands
643 let h_commands = { // eslint-disable-next-line quote-props
644 // 'content': (a_args, g_context) => {
645 // let g_argv = command(g_context.command)
646 // .string('t')
647 // .alias('t', ['type'])
648 // .describe('t', 'argument to `super.content()`; either an RDF Content-Type or format selector')
649 // .string('v')
650 // .alias('v', 'verb')
651 // .describe('v', 'which verb to access on the given content handler, e.g., `read`, `write`, etc.')
652 // .help()
653 // .version(false)
654 // .parse(a_args);
655
656 // },
657
658 'content.nt.read': reader(graphy.content.nt.read),
659 'content.nq.read': reader(graphy.content.nq.read),
660 'content.ttl.read': reader(graphy.content.ttl.read),
661 'content.trig.read': reader(graphy.content.trig.read),
662
663 'content.nt.write': writer(graphy.content.nt.write),
664 'content.nq.write': writer(graphy.content.nq.write),
665 'content.ttl.write': writer(graphy.content.ttl.write),
666 'content.trig.write': writer(graphy.content.trig.write),
667
668 'util.dataset.tree': async(a_args, g_context) => {
669 const dataset_tree = graphy.util.dataset.tree;
670
671 let s_group_multi_input = 'Transform 1 or more inputs to 1 output:';
672 let s_group_dual_input = 'Transform exactly 2 inputs into 1 output:';
673 let s_group_boolean = 'Test exactly 2 inputs to get `true` or `false`:';
674
675 let h_operations = {
676 z: {
677 type: 'boolean',
678 alias: ['canonicalize'],
679 group: s_group_multi_input,
680 describe: 'canonicalize 1 or more inputs',
681 },
682 u: {
683 type: 'boolean',
684 alias: ['union'],
685 group: s_group_multi_input,
686 describe: 'perform the union of 1 or more inputs',
687 },
688 i: {
689 type: 'boolean',
690 alias: ['intersect', 'intersection'],
691 group: s_group_multi_input,
692 describe: 'perform the intersection of 1 or more inputs',
693 },
694 d: {
695 type: 'boolean',
696 alias: ['diff', 'difference'],
697 group: s_group_dual_input,
698 describe: 'perform a difference between two inputs',
699 },
700 m: {
701 type: 'boolean',
702 alias: ['minus', 'subtract', 'subtraction'],
703 group: s_group_dual_input,
704 describe: 'perform a subtraction by removing input-B from input-A',
705 },
706 c: {
707 type: 'boolean',
708 alias: ['contains'],
709 group: s_group_boolean,
710 describe: 'test if input-A completely contains input-B, i.e., if B is a subset of A',
711 },
712 j: {
713 type: 'boolean',
714 alias: ['disjoint'],
715 group: s_group_boolean,
716 describe: 'test if input-A is disjoint with input-B',
717 },
718 e: {
719 type: 'boolean',
720 alias: ['equals'],
721 group: s_group_boolean,
722 describe: 'test if input-A is exactly equal to input-B (you can test for isomorphism by piping thru --canonicalize first)',
723 },
724 };
725
726 let a_operation_keys = Object.keys(h_operations);
727 for(let s_operation of a_operation_keys) {
728 h_operations[s_operation].conflicts = a_operation_keys.filter(s => s_operation !== s);
729 }
730
731 let g_argv = command(g_context.command)
732 .options(h_operations)
733 .help()
734 .version(false)
735 .parse(a_args);
736
737 // ref inputs; cache length
738 let a_inputs = g_context.inputs;
739 let n_inputs = a_inputs.length;
740
741 // multi-input stream-output operation
742 if(g_argv.union || g_argv.intersection) {
743 let s_operation = g_argv.union
744 ? 'union'
745 : 'intersection';
746
747 // // less than 2 inputs; no-op
748 // if(n_inputs < 2) return a_inputs;
749
750 // create trees
751 let a_trees = a_inputs.map(() => dataset_tree());
752
753 // initial tree
754 let k_tree_out = a_trees[0];
755
756 // pairwise readiness
757 for(let i_input=0; i_input<n_inputs; i_input++) {
758 let k_tree_b = a_trees[i_input];
759
760 // pipe input stream to tree b
761 a_inputs[i_input].pipe(k_tree_b);
762
763 // wait for input stream to finish writing to b
764 await k_tree_b.until('finish');
765
766 // non-first input
767 if(i_input) {
768 // perform pairwise operation
769 k_tree_out = k_tree_out[s_operation](k_tree_b);
770 }
771 }
772
773 // return readable tree
774 return [k_tree_out];
775 }
776 // dual-input stream-output operation
777 else if(g_argv.difference || g_argv.subtraction) {
778 let s_operation = g_argv.difference
779 ? 'difference'
780 : 'minus';
781
782 // not two inputs
783 if(2 !== n_inputs) {
784 exit(`operation '${s_operation}' expects two inputs but found ${n_inputs}`);
785 }
786
787 // async
788 return new Promise((fk_resolve) => {
789 let operate = () => [k_tree_a[s_operation](k_tree_b)];
790
791 // wait for a
792 let k_tree_a = dataset_tree();
793 let b_finished_a = false;
794 k_tree_a.on('finish', () => {
795 b_finished_a = true;
796 if(b_finished_b) fk_resolve(operate());
797 });
798
799 // wait for b
800 let k_tree_b = dataset_tree();
801 let b_finished_b = false;
802 k_tree_b.on('finish', () => {
803 b_finished_b = true;
804 if(b_finished_a) fk_resolve(operate());
805 });
806
807 // ref both input streams
808 let [ds_input_a, ds_input_b] = a_inputs;
809
810 // pipe each to its tree
811 ds_input_a.pipe(k_tree_a);
812 ds_input_b.pipe(k_tree_b);
813 });
814 }
815 // boolean
816 else if(g_argv.contains || g_argv.disjoint || g_argv.equals) {
817 let s_operation = g_argv.contains
818 ? 'contains'
819 : (g_argv.disjoint
820 ? 'disjoint'
821 : 'equals');
822
823 // not two inputs
824 if(2 !== n_inputs) {
825 exit(`boolean operation '${s_operation}' expects two inputs but found ${n_inputs}`);
826 }
827
828 // async
829 return new Promise((fk_resolve) => {
830 let operate = () => [new answer_source(k_tree_a[s_operation](k_tree_b))];
831
832 // wait for a
833 let k_tree_a = dataset_tree();
834 let b_finished_a = false;
835 k_tree_a.on('finish', () => {
836 b_finished_a = true;
837 if(b_finished_b) fk_resolve(operate());
838 });
839
840 // wait for b
841 let k_tree_b = dataset_tree();
842 let b_finished_b = false;
843 k_tree_b.on('finish', () => {
844 b_finished_b = true;
845 if(b_finished_a) fk_resolve(operate());
846 });
847
848 // ref both input streams
849 let [ds_input_a, ds_input_b] = a_inputs;
850
851 // pipe each to its tree
852 ds_input_a.pipe(k_tree_a);
853 ds_input_b.pipe(k_tree_b);
854 });
855 }
856 // map; n-to-n
857 else {
858 return g_context.inputs.map(ds_input => ds_input.pipe(dataset_tree({
859 canonicalize: g_argv.canonicalize,
860 })));
861 }
862 },
863 };
864
865 let a_argv = process.argv.slice(2);
866 let n_args = a_argv.length;
867
868 // no arguments
869 if(!a_argv.length) {
870 exit('no arguments given');
871 }
872
873 // inputs
874 let a_inputs = [];
875
876 // pipeline
877 let a_pipeline = [];
878 {
879 let a_series = [];
880
881 for(let i_argv=0; i_argv<n_args; i_argv++) {
882 let s_arg = a_argv[i_argv];
883
884 // after first arg
885 if(i_argv) {
886 // internal pipe
887 if('--pipe' === s_arg) {
888 a_pipeline.push(a_series);
889 if(i_argv === n_args) {
890 exit(`was expecting pipe destination after --pipe: ${a_argv}`);
891 }
892 a_series = [];
893 continue;
894 }
895 // inputs follow
896 else if('--inputs' === s_arg) {
897 // convert to readable streams
898 a_inputs.push(...a_argv.slice(i_argv+1).map(p => fs.createReadStream(p)));
899 break;
900 }
901 }
902
903 // help
904 if('-h' === s_arg || '--help' === s_arg) {
905 // eslint-disable-next-line no-console
906 console.log('\n'+gobble(`
907 Usage: graphy COMMAND [--pipe COMMAND]*
908
909 Commands:
910 content.nt.read Read an N-Triples document
911 content.nt.write Write to N-Triples format
912 content.nq.read Read an N-Quads document
913 content.nq.write Write to N-Quads format
914 content.ttl.read Read a Turtle document
915 content.ttl.write Write to Turtle format
916 content.trig.read Read a TriG document
917 content.trig.write Write to TriG format
918 util.dataset.tree Perform some transformation on a datset
919
920 Run 'graphy COMMAND --help' for more information on a command.
921 `));
922 process.exit(0);
923 }
924 // version
925 else if('-v' === s_arg || '--version' === s_arg) {
926 // eslint-disable-next-line no-console
927 console.log(require(path.join(__dirname, './package.json')).version);
928 process.exit(0);
929 }
930
931 a_series.push(s_arg);
932 }
933
934 // empty series
935 if(a_series.length) {
936 a_pipeline.push(a_series);
937 }
938 }
939
940 // empty command list
941 if(!a_pipeline.length) {
942 exit('no commands given');
943 }
944
945 // const parse_series = a_args => mk_yargs()
946 // .command('content.ttl.read', 'read Turtle document', (yargs_read) => {
947 // return yargs_read
948 // .options({
949 // u: {
950 // type: 'boolean',
951 // default: false,
952 // alias: 'union',
953 // group: s_group_multi_input,
954 // describe: 'perform the union of multiple inputs',
955 // conflicts: a_dual_input_keys.filter(s => 'u' !== s),
956 // },
957 // i: {
958 // type: 'boolean',
959 // default: false,
960 // alias: ['intersect', 'intersection'],
961 // group: s_group_multi_input,
962 // describe: 'perform the intersection of multiple inputs',
963 // conflicts: a_dual_input_keys.filter(s => 'i' !== s),
964 // },
965 // d: {
966 // type: 'boolean',
967 // alias: ['diff', 'difference'],
968 // group: s_group_dual_input,
969 // describe: 'perform a difference between two inputs',
970 // conflicts: a_dual_input_keys.filter(s => 'd' !== s),
971 // },
972 // m: {
973 // type: 'boolean',
974 // alias: ['minus', 'subtract', 'subtraction'],
975 // group: s_group_dual_input,
976 // describe: 'perform a subtraction by removing input-B from input-A',
977 // conflicts: a_dual_input_keys.filter(s => 'm' !== s),
978 // },
979 // })
980 // .help()
981 // .parse();
982 // })
983 // .command('util.dataset.tree')
984 // .help()
985 // .parse(a_args);
986
987 (async() => {
988 // failure handler
989 let f_failure = (e_command) => {
990 exit(e_command.message);
991 };
992
993 // starting inputs default to stdin if no explicit inputs given
994 let a_prev = a_inputs.length? a_inputs: [process.stdin];
995
996 // each series in pipeline
997 for(let a_series of a_pipeline) {
998 // start with command string
999 let s_command = a_series[0];
1000
1001 // no such command
1002 if(!(s_command in h_commands)) {
1003 exit(`no such command '${s_command}'`);
1004 }
1005
1006 try {
1007 // eval command with its args
1008 let a_curr = await h_commands[s_command](a_series.slice(1), {
1009 command: s_command,
1010 inputs: a_prev,
1011 failure: f_failure,
1012 });
1013
1014 // advance inputs
1015 a_prev = a_curr;
1016 }
1017 catch(e_command) {
1018 exit(e_command.message);
1019 }
1020 }
1021
1022 // expect single output
1023 if(1 !== a_prev.length) {
1024 exit(`expected a single output stream but last command produces ${a_prev.length} streams`);
1025 }
1026
1027 // pipe output to stdout
1028 a_prev[0].pipe(process.stdout);
1029 })();
1030}