UNPKG

18.9 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3
4const gobble = (s_text, s_indent='') => {
5 let m_pad = /^(\s+)/.exec(s_text.replace(/^([ \t]*\n)/, ''));
6 if(m_pad) {
7 return s_indent+s_text.replace(new RegExp(`\\n${m_pad[1]}`, 'g'), '\n'+s_indent.trim()).trim();
8 }
9 else {
10 return s_indent+s_text.trim();
11 }
12};
13
14
15
16 // ttl package
17const ttl = {
18 // read ttl output
19 get read() {
20 // memoize
21 delete ttl.read;
22 return ttl.read = require('@graphy-dev/content.ttl.read'); // eslint-disable-line global-require
23 },
24 // scan ttl output
25 get scan() {
26 // memoize
27 delete ttl.scan;
28 return ttl.scan = require('@graphy-dev/content.ttl.scan'); // eslint-disable-line global-require
29 },
30 // write ttl input
31 get write() {
32 // memoize
33 delete ttl.write;
34 return ttl.write = require('@graphy-dev/content.ttl.write'); // eslint-disable-line global-require
35 },
36};
37 // trig package
38const trig = {
39 // read trig output
40 get read() {
41 // memoize
42 delete trig.read;
43 return trig.read = require('@graphy-dev/content.trig.read'); // eslint-disable-line global-require
44 },
45 // scan trig output
46 get scan() {
47 // memoize
48 delete trig.scan;
49 return trig.scan = require('@graphy-dev/content.trig.scan'); // eslint-disable-line global-require
50 },
51 // write trig input
52 get write() {
53 // memoize
54 delete trig.write;
55 return trig.write = require('@graphy-dev/content.trig.write'); // eslint-disable-line global-require
56 },
57};
58 // nt package
59const nt = {
60 // read nt output
61 get read() {
62 // memoize
63 delete nt.read;
64 return nt.read = require('@graphy-dev/content.nt.read'); // eslint-disable-line global-require
65 },
66 // scan nt output
67 get scan() {
68 // memoize
69 delete nt.scan;
70 return nt.scan = require('@graphy-dev/content.nt.scan'); // eslint-disable-line global-require
71 },
72 // write nt input
73 get write() {
74 // memoize
75 delete nt.write;
76 return nt.write = require('@graphy-dev/content.nt.write'); // eslint-disable-line global-require
77 },
78};
79 // nq package
80const nq = {
81 // read nq output
82 get read() {
83 // memoize
84 delete nq.read;
85 return nq.read = require('@graphy-dev/content.nq.read'); // eslint-disable-line global-require
86 },
87 // scan nq output
88 get scan() {
89 // memoize
90 delete nq.scan;
91 return nq.scan = require('@graphy-dev/content.nq.scan'); // eslint-disable-line global-require
92 },
93 // write nq input
94 get write() {
95 // memoize
96 delete nq.write;
97 return nq.write = require('@graphy-dev/content.nq.write'); // eslint-disable-line global-require
98 },
99};
100
101
102// // SPARQL Results package
103// const sparql_results = {
104// // deserialize sparql_results input
105// get deserializer() {
106// // memoize
107// delete sparql_results.deserializer;
108// return (sparql_results.deserializer = require('../sparql-results/deserializer.js'));
109// },
110// };
111
112
113const H_CONTENT_MIMES = {
114 'text/turtle': ttl,
115 'application/trig': trig,
116 'application/n-triples': nt,
117 'application/n-quads': nq,
118 // 'application/sparql-results+json': sparql_results,
119};
120
121const H_CONTENT_TAGS = {
122 ttl,
123 trig,
124 nt,
125 nq,
126 // 'application/sparql-results+json': sparql_results,
127};
128
129
130
131const R_CONTENT_TYPE = /^((?:application|text)\/[^\0-\x20()<>@,;:\\"\/[\]?.=]+)(;.+)*$/i;
132
133const graphy = module.exports = Object.assign({
134
135 content: Object.assign((s_query) => {
136 if(s_query in H_CONTENT_TAGS) {
137 return H_CONTENT_TAGS[s_query];
138 }
139
140 let m_content_type = R_CONTENT_TYPE.exec(s_query);
141 if(!m_content_type) throw new Error(`invalid content-type string: "${s_query}"`);
142 let [, s_content_type, s_parameters] = m_content_type;
143 let s_content_type_normal = s_content_type.toLowerCase();
144
145 if(s_content_type_normal in H_CONTENT_MIMES) {
146 return H_CONTENT_MIMES[s_content_type_normal];
147 }
148 else {
149 throw new Error(`no content handlers matched query for "${s_content_type_normal}"`);
150 }
151 }, {
152 ttl,
153 trig,
154 nt,
155 nq,
156
157 }),
158
159 core: {
160 data: {
161 get factory() {
162 // memoize
163 delete graphy.core.data.factory;
164 return (graphy.core.data.factory = require('@graphy-dev/core.data.factory'));
165 },
166 },
167 },
168
169 get 'core.data.factory'() {
170 delete graphy['core.data.factory'];
171 return (graphy['core.data.factory'] = require('@graphy-dev/core.data.factory'));
172 },
173
174 get 'util.dataset.tree'() {
175 delete graphy['util.dataset.tree'];
176 return (graphy['util.dataset.tree'] = require('@graphy-dev/util.dataset.tree'));
177 },
178
179 util: {
180 dataset: {
181 get tree() {
182 // memoize
183 delete graphy.util.dataset.tree;
184 return (graphy.util.dataset.tree = require('@graphy-dev/util.dataset.tree'));
185 },
186 },
187 },
188
189
190 get 'content.ttl.read'() {
191 // memoize
192 delete graphy['content.ttl.read'];
193 return graphy['content.ttl.read'] = require('@graphy-dev/content.ttl.read'); // eslint-disable-line global-require
194 },
195
196 get 'content.ttl.write'() {
197 // memoize
198 delete graphy['content.ttl.write'];
199 return graphy['content.ttl.write'] = require('@graphy-dev/content.ttl.write'); // eslint-disable-line global-require
200 },
201
202 get 'content.trig.read'() {
203 // memoize
204 delete graphy['content.trig.read'];
205 return graphy['content.trig.read'] = require('@graphy-dev/content.trig.read'); // eslint-disable-line global-require
206 },
207
208 get 'content.trig.write'() {
209 // memoize
210 delete graphy['content.trig.write'];
211 return graphy['content.trig.write'] = require('@graphy-dev/content.trig.write'); // eslint-disable-line global-require
212 },
213
214 get 'content.nt.read'() {
215 // memoize
216 delete graphy['content.nt.read'];
217 return graphy['content.nt.read'] = require('@graphy-dev/content.nt.read'); // eslint-disable-line global-require
218 },
219
220 get 'content.nt.write'() {
221 // memoize
222 delete graphy['content.nt.write'];
223 return graphy['content.nt.write'] = require('@graphy-dev/content.nt.write'); // eslint-disable-line global-require
224 },
225
226 get 'content.nq.read'() {
227 // memoize
228 delete graphy['content.nq.read'];
229 return graphy['content.nq.read'] = require('@graphy-dev/content.nq.read'); // eslint-disable-line global-require
230 },
231
232 get 'content.nq.write'() {
233 // memoize
234 delete graphy['content.nq.write'];
235 return graphy['content.nq.write'] = require('@graphy-dev/content.nq.write'); // eslint-disable-line global-require
236 },
237
238
239
240}, require('@graphy-dev/core.data.factory'));
241
242
243// export graphy to window object if in main thread of browser
244if('undefined' !== typeof window) window.graphy = graphy;
245
246// cli
247if(module === require.main) {
248 const fs =require('fs');
249 const path = require('path');
250 const mk_yargs = require('yargs/yargs');
251 const stream = require('@graphy-dev/core.iso.stream');
252
253 class answer_source extends require('stream').Readable {
254 constructor(w_datum) {
255 super({
256 objectMode: true,
257 });
258
259 this.datum = w_datum;
260 }
261
262 // intercept pipe
263 pipe(ds_dst) {
264 // string out
265 if(!ds_dst._writableState.objectMode) {
266 // change read mode; push as JSON
267 this._read = () => {
268 this.push(JSON.stringify(this.datum)+'\n', 'utf8');
269 this.push(null);
270 };
271 }
272
273 // forward to super
274 return super.pipe(ds_dst);
275 }
276
277 // push object
278 _read() {
279 this.push(this.datum);
280 this.push(this.null);
281 }
282 }
283
284 const exit = (s_exit) => {
285 console.error(s_exit);
286 process.exit(1);
287 };
288
289 const command = s_command => mk_yargs()
290 .strict()
291 .usage(`Usage: $0 ${s_command} [OPTIONS] [--pipe COMMAND]`);
292
293 const reader = f_reader => (a_args, g_context) => new Promise((fk_resolve) => {
294 let g_argv = command(g_context.command)
295 .boolean('v')
296 .alias('v', 'validate')
297 .describe('v', 'validate all tokens within the RDF document')
298 .string('b')
299 .alias('b', ['base', 'base-uri'])
300 .describe('b', 'set a base URI on the document')
301 .help()
302 .version(false)
303 .parse(a_args);
304
305 let gc_read = {
306 validate: g_argv.validate || false,
307 };
308
309 if(g_argv['base-uri']) {
310 gc_read.baseUri = g_argv['base-uri'];
311 }
312
313 fk_resolve(g_context.inputs.map((ds_input) => {
314 let ds_reader = f_reader({
315 ...gc_read,
316 error(e_read) {
317 g_context.failure(e_read);
318 },
319 });
320
321 return ds_input.pipe(ds_reader);
322 }));
323 });
324
325 const writer = f_writer => (a_args, g_context) => {
326 let g_argv = command(g_context.command)
327 .help()
328 .parse(a_args);
329
330 let gc_write = {};
331
332 return g_context.inputs.map((ds_input) => {
333 let ds_writer = f_writer({
334 ...gc_write,
335 error(e_write) {
336 g_context.failure(e_write);
337 },
338 });
339 return ds_input.pipe(ds_writer);
340 });
341 };
342
343 // commands
344 let h_commands = { // eslint-disable-next-line quote-props
345 // 'content': (a_args, g_context) => {
346 // let g_argv = command(g_context.command)
347 // .string('t')
348 // .alias('t', ['type'])
349 // .describe('t', 'argument to `super.content()`; either an RDF Content-Type or format selector')
350 // .string('v')
351 // .alias('v', 'verb')
352 // .describe('v', 'which verb to access on the given content handler, e.g., `read`, `write`, etc.')
353 // .help()
354 // .version(false)
355 // .parse(a_args);
356
357 // },
358
359 'content.nt.read': reader(graphy.content.nt.read),
360 'content.nq.read': reader(graphy.content.nq.read),
361 'content.ttl.read': reader(graphy.content.ttl.read),
362 'content.trig.read': reader(graphy.content.trig.read),
363
364 'content.nt.write': writer(graphy.content.nt.write),
365 'content.nq.write': writer(graphy.content.nq.write),
366 'content.ttl.write': writer(graphy.content.ttl.write),
367 'content.trig.write': writer(graphy.content.trig.write),
368
369 'util.dataset.tree': async(a_args, g_context) => {
370 const dataset_tree = graphy.util.dataset.tree;
371
372 let s_group_multi_input = 'Transform 1 or more inputs to 1 output:';
373 let s_group_dual_input = 'Transform exactly 2 inputs into 1 output:';
374 let s_group_boolean = 'Test exactly 2 inputs to get `true` or `false`:';
375
376 let h_operations = {
377 z: {
378 type: 'boolean',
379 alias: ['canonicalize'],
380 group: s_group_multi_input,
381 describe: 'canonicalize 1 or more inputs',
382 },
383 u: {
384 type: 'boolean',
385 alias: ['union'],
386 group: s_group_multi_input,
387 describe: 'perform the union of 1 or more inputs',
388 },
389 i: {
390 type: 'boolean',
391 alias: ['intersect', 'intersection'],
392 group: s_group_multi_input,
393 describe: 'perform the intersection of 1 or more inputs',
394 },
395 d: {
396 type: 'boolean',
397 alias: ['diff', 'difference'],
398 group: s_group_dual_input,
399 describe: 'perform a difference between two inputs',
400 },
401 m: {
402 type: 'boolean',
403 alias: ['minus', 'subtract', 'subtraction'],
404 group: s_group_dual_input,
405 describe: 'perform a subtraction by removing input-B from input-A',
406 },
407 c: {
408 type: 'boolean',
409 alias: ['contains'],
410 group: s_group_boolean,
411 describe: 'test if input-A completely contains input-B, i.e., if B is a subset of A',
412 },
413 j: {
414 type: 'boolean',
415 alias: ['disjoint'],
416 group: s_group_boolean,
417 describe: 'test if input-A is disjoint with input-B',
418 },
419 e: {
420 type: 'boolean',
421 alias: ['equals'],
422 group: s_group_boolean,
423 describe: 'test if input-A is exactly equal to input-B (you can test for isomorphism by piping thru --canonicalize first)',
424 },
425 };
426
427 let a_operation_keys = Object.keys(h_operations);
428 for(let s_operation of a_operation_keys) {
429 h_operations[s_operation].conflicts = a_operation_keys.filter(s => s_operation !== s);
430 }
431
432 let g_argv = command(g_context.command)
433 .options(h_operations)
434 .help()
435 .version(false)
436 .parse(a_args);
437
438 // ref inputs; cache length
439 let a_inputs = g_context.inputs;
440 let n_inputs = a_inputs.length;
441
442 // multi-input stream-output operation
443 if(g_argv.union || g_argv.intersection) {
444 let s_operation = g_argv.union
445 ? 'union'
446 : 'intersection';
447
448 // // less than 2 inputs; no-op
449 // if(n_inputs < 2) return a_inputs;
450
451 // create trees
452 let a_trees = a_inputs.map(() => dataset_tree());
453
454 // initial tree
455 let k_tree_out = a_trees[0];
456
457 // pairwise readiness
458 for(let i_input=0; i_input<n_inputs; i_input++) {
459 let k_tree_b = a_trees[i_input];
460
461 // pipe input stream to tree b
462 a_inputs[i_input].pipe(k_tree_b);
463
464 // wait for input stream to finish writing to b
465 await k_tree_b.until('finish');
466
467 // non-first input
468 if(i_input) {
469 // perform pairwise operation
470 k_tree_out = k_tree_out[s_operation](k_tree_b);
471 }
472 }
473
474 // return readable tree
475 return [k_tree_out];
476 }
477 // dual-input stream-output operation
478 else if(g_argv.difference || g_argv.subtraction) {
479 let s_operation = g_argv.difference
480 ? 'difference'
481 : 'minus';
482
483 // not two inputs
484 if(2 !== n_inputs) {
485 exit(`operation '${s_operation}' expects two inputs but found ${n_inputs}`);
486 }
487
488 // async
489 return new Promise((fk_resolve) => {
490 let operate = () => [k_tree_a[s_operation](k_tree_b)];
491
492 // wait for a
493 let k_tree_a = dataset_tree();
494 let b_finished_a = false;
495 k_tree_a.on('finish', () => {
496 b_finished_a = true;
497 if(b_finished_b) fk_resolve(operate());
498 });
499
500 // wait for b
501 let k_tree_b = dataset_tree();
502 let b_finished_b = false;
503 k_tree_b.on('finish', () => {
504 b_finished_b = true;
505 if(b_finished_a) fk_resolve(operate());
506 });
507
508 // ref both input streams
509 let [ds_input_a, ds_input_b] = a_inputs;
510
511 // pipe each to its tree
512 ds_input_a.pipe(k_tree_a);
513 ds_input_b.pipe(k_tree_b);
514 });
515 }
516 // boolean
517 else if(g_argv.contains || g_argv.disjoint || g_argv.equals) {
518 let s_operation = g_argv.contains
519 ? 'contains'
520 : (g_argv.disjoint
521 ? 'disjoint'
522 : 'equals');
523
524 // not two inputs
525 if(2 !== n_inputs) {
526 exit(`boolean operation '${s_operation}' expects two inputs but found ${n_inputs}`);
527 }
528
529 // async
530 return new Promise((fk_resolve) => {
531 let operate = () => [new answer_source(k_tree_a[s_operation](k_tree_b))];
532
533 // wait for a
534 let k_tree_a = dataset_tree();
535 let b_finished_a = false;
536 k_tree_a.on('finish', () => {
537 b_finished_a = true;
538 if(b_finished_b) fk_resolve(operate());
539 });
540
541 // wait for b
542 let k_tree_b = dataset_tree();
543 let b_finished_b = false;
544 k_tree_b.on('finish', () => {
545 b_finished_b = true;
546 if(b_finished_a) fk_resolve(operate());
547 });
548
549 // ref both input streams
550 let [ds_input_a, ds_input_b] = a_inputs;
551
552 // pipe each to its tree
553 ds_input_a.pipe(k_tree_a);
554 ds_input_b.pipe(k_tree_b);
555 });
556 }
557 // map; n-to-n
558 else {
559 return g_context.inputs.map(ds_input => ds_input.pipe(dataset_tree({
560 canonicalize: g_argv.canonicalize,
561 })));
562 }
563 },
564 };
565
566 let a_argv = process.argv.slice(2);
567 let n_args = a_argv.length;
568
569 // no arguments
570 if(!a_argv.length) {
571 exit('no arguments given');
572 }
573
574 // inputs
575 let a_inputs = [];
576
577 // pipeline
578 let a_pipeline = [];
579 {
580 let a_series = [];
581
582 for(let i_argv=0; i_argv<n_args; i_argv++) {
583 let s_arg = a_argv[i_argv];
584
585 // after first arg
586 if(i_argv) {
587 // internal pipe
588 if('--pipe' === s_arg) {
589 a_pipeline.push(a_series);
590 if(i_argv === n_args) {
591 exit(`was expecting pipe destination after --pipe: ${a_argv}`);
592 }
593 a_series = [];
594 continue;
595 }
596 // inputs follow
597 else if('--inputs' === s_arg) {
598 // convert to readable streams
599 a_inputs.push(...a_argv.slice(i_argv+1).map(p => fs.createReadStream(p)));
600 break;
601 }
602 }
603
604 // help
605 if('-h' === s_arg || '--help' === s_arg) {
606 // eslint-disable-next-line no-console
607 console.log('\n'+gobble(`
608 Usage: graphy COMMAND [--pipe COMMAND]*
609
610 Commands:
611 content.nt.read Read an N-Triples document
612 content.nt.write Write to N-Triples format
613 content.nq.read Read an N-Quads document
614 content.nq.write Write to N-Quads format
615 content.ttl.read Read a Turtle document
616 content.ttl.write Write to Turtle format
617 content.trig.read Read a TriG document
618 content.trig.write Write to TriG format
619 util.dataset.tree Perform some transformation on a datset
620
621 Run 'graphy COMMAND --help' for more information on a command.
622 `));
623 process.exit(0);
624 }
625 // version
626 else if('-v' === s_arg || '--version' === s_arg) {
627 // eslint-disable-next-line no-console
628 console.log(require(path.join(__dirname, './package.json')).version);
629 process.exit(0);
630 }
631
632 a_series.push(s_arg);
633 }
634
635 // empty series
636 if(a_series.length) {
637 a_pipeline.push(a_series);
638 }
639 }
640
641 // empty command list
642 if(!a_pipeline.length) {
643 exit('no commands given');
644 }
645
646 // const parse_series = a_args => mk_yargs()
647 // .command('content.ttl.read', 'read Turtle document', (yargs_read) => {
648 // return yargs_read
649 // .options({
650 // u: {
651 // type: 'boolean',
652 // default: false,
653 // alias: 'union',
654 // group: s_group_multi_input,
655 // describe: 'perform the union of multiple inputs',
656 // conflicts: a_dual_input_keys.filter(s => 'u' !== s),
657 // },
658 // i: {
659 // type: 'boolean',
660 // default: false,
661 // alias: ['intersect', 'intersection'],
662 // group: s_group_multi_input,
663 // describe: 'perform the intersection of multiple inputs',
664 // conflicts: a_dual_input_keys.filter(s => 'i' !== s),
665 // },
666 // d: {
667 // type: 'boolean',
668 // alias: ['diff', 'difference'],
669 // group: s_group_dual_input,
670 // describe: 'perform a difference between two inputs',
671 // conflicts: a_dual_input_keys.filter(s => 'd' !== s),
672 // },
673 // m: {
674 // type: 'boolean',
675 // alias: ['minus', 'subtract', 'subtraction'],
676 // group: s_group_dual_input,
677 // describe: 'perform a subtraction by removing input-B from input-A',
678 // conflicts: a_dual_input_keys.filter(s => 'm' !== s),
679 // },
680 // })
681 // .help()
682 // .parse();
683 // })
684 // .command('util.dataset.tree')
685 // .help()
686 // .parse(a_args);
687
688 (async() => {
689 // failure handler
690 let f_failure = (e_command) => {
691 exit(e_command.message);
692 };
693
694 // starting inputs default to stdin if no explicit inputs given
695 let a_prev = a_inputs.length? a_inputs: [process.stdin];
696
697 // each series in pipeline
698 for(let a_series of a_pipeline) {
699 // start with command string
700 let s_command = a_series[0];
701
702 // no such command
703 if(!(s_command in h_commands)) {
704 exit(`no such command '${s_command}'`);
705 }
706
707 try {
708 // eval command with its args
709 let a_curr = await h_commands[s_command](a_series.slice(1), {
710 command: s_command,
711 inputs: a_prev,
712 failure: f_failure,
713 });
714
715 // advance inputs
716 a_prev = a_curr;
717 }
718 catch(e_command) {
719 exit(e_command.message);
720 }
721 }
722
723 // expect single output
724 if(1 !== a_prev.length) {
725 exit(`expected a single output stream but last command produces ${a_prev.length} streams`);
726 }
727
728 // pipe output to stdout
729 a_prev[0].pipe(process.stdout);
730 })();
731}