UNPKG

12.8 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3
4
5 // ttl package
6const ttl = {
7 // read ttl output
8 get read() {
9 // memoize
10 delete ttl.read;
11 return ttl.read = require('@graphy-dev/content.ttl.read'); // eslint-disable-line global-require
12 },
13 // scan ttl output
14 get scan() {
15 // memoize
16 delete ttl.scan;
17 return ttl.scan = require('@graphy-dev/content.ttl.scan'); // eslint-disable-line global-require
18 },
19 // write ttl input
20 get write() {
21 // memoize
22 delete ttl.write;
23 return ttl.write = require('@graphy-dev/content.ttl.write'); // eslint-disable-line global-require
24 },
25};
26 // trig package
27const trig = {
28 // read trig output
29 get read() {
30 // memoize
31 delete trig.read;
32 return trig.read = require('@graphy-dev/content.trig.read'); // eslint-disable-line global-require
33 },
34 // scan trig output
35 get scan() {
36 // memoize
37 delete trig.scan;
38 return trig.scan = require('@graphy-dev/content.trig.scan'); // eslint-disable-line global-require
39 },
40 // write trig input
41 get write() {
42 // memoize
43 delete trig.write;
44 return trig.write = require('@graphy-dev/content.trig.write'); // eslint-disable-line global-require
45 },
46};
47 // nt package
48const nt = {
49 // read nt output
50 get read() {
51 // memoize
52 delete nt.read;
53 return nt.read = require('@graphy-dev/content.nt.read'); // eslint-disable-line global-require
54 },
55 // scan nt output
56 get scan() {
57 // memoize
58 delete nt.scan;
59 return nt.scan = require('@graphy-dev/content.nt.scan'); // eslint-disable-line global-require
60 },
61 // write nt input
62 get write() {
63 // memoize
64 delete nt.write;
65 return nt.write = require('@graphy-dev/content.nt.write'); // eslint-disable-line global-require
66 },
67};
68 // nq package
69const nq = {
70 // read nq output
71 get read() {
72 // memoize
73 delete nq.read;
74 return nq.read = require('@graphy-dev/content.nq.read'); // eslint-disable-line global-require
75 },
76 // scan nq output
77 get scan() {
78 // memoize
79 delete nq.scan;
80 return nq.scan = require('@graphy-dev/content.nq.scan'); // eslint-disable-line global-require
81 },
82 // write nq input
83 get write() {
84 // memoize
85 delete nq.write;
86 return nq.write = require('@graphy-dev/content.nq.write'); // eslint-disable-line global-require
87 },
88};
89
90
91// // SPARQL Results package
92// const sparql_results = {
93// // deserialize sparql_results input
94// get deserializer() {
95// // memoize
96// delete sparql_results.deserializer;
97// return (sparql_results.deserializer = require('../sparql-results/deserializer.js'));
98// },
99// };
100
101
102const H_CONTENT_MIMES = {
103 'text/turtle': ttl,
104 'application/trig': trig,
105 'application/n-triples': nt,
106 'application/n-quads': nq,
107 // 'application/sparql-results+json': sparql_results,
108};
109
110const H_CONTENT_TAGS = {
111 ttl,
112 trig,
113 nt,
114 nq,
115 // 'application/sparql-results+json': sparql_results,
116};
117
118
119
120const R_CONTENT_TYPE = /^((?:application|text)\/[^\0-\x20()<>@,;:\\"\/[\]?.=]+)(;.+)*$/i;
121
122const 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 // memoize
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// export graphy to window object if in main thread of browser
162if('undefined' !== typeof window) window.graphy = graphy;
163
164// cli
165if(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 // commands
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 // ref inputs; cache length
273 let a_inputs = g_context.inputs;
274 let n_inputs = a_inputs.length;
275
276 // multi-input stream-output operation
277 if(g_argv.union || g_argv.intersection) {
278 let s_operation = g_argv.union
279 ? 'union'
280 : 'intersection';
281
282 // less than 2 inputs; no-op
283 if(n_inputs < 2) return a_inputs;
284
285 // create trees
286 let a_trees = a_inputs.map(() => dataset_tree());
287
288 // initial tree
289 let k_tree_out = a_trees[0];
290
291 // pairwise readiness
292 for(let i_input=0; i_input<n_inputs; i_input++) {
293 let k_tree_b = a_trees[i_input];
294
295 // pipe input stream to tree b
296 a_inputs[i_input].pipe(k_tree_b);
297
298 // wait for input stream to finish writing to b
299 await k_tree_b.until('finish');
300
301 // non-first input
302 if(i_input) {
303 // perform pairwise operation
304 k_tree_out = k_tree_out[s_operation](k_tree_b);
305 }
306 }
307
308 // return readable tree
309 return [k_tree_out];
310 }
311 // dual-input stream-output operation
312 else if(g_argv.difference || g_argv.subtraction) {
313 let s_operation = g_argv.difference
314 ? 'difference'
315 : 'minus';
316
317 // not two inputs
318 if(2 !== n_inputs) {
319 exit(`dual input operation expects two inputs but found ${n_inputs}`);
320 }
321
322 // async
323 return new Promise((fk_resolve) => {
324 let operate = () => [k_tree_a[s_operation](k_tree_b)];
325
326 // wait for a
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 // wait for b
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 // ref both input streams
343 let [ds_input_a, ds_input_b] = a_inputs;
344
345 // pipe each to its tree
346 ds_input_a.pipe(k_tree_a);
347 ds_input_b.pipe(k_tree_b);
348 });
349 }
350 // reduction; n-to-n
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 // inputs
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 // input
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 // pipeline
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 // inspect last arguments
407 {
408 let a_series = a_pipeline[a_pipeline.length-1];
409 let n_series = a_series.length;
410
411 // backwards
412 for(let i_end=n_series-1; i_end>=0; i_end--) {
413 // arg is path
414 if(a_series[i_end].startsWith('/')) {
415 // append as input
416 a_inputs.push(fs.createReadStream(a_series.pop()));
417
418 // keep going
419 continue;
420 }
421
422 // stop eating args
423 break;
424 }
425
426 // series now empty; pop from pipeline
427 if(!a_series.length) a_pipeline.pop();
428 }
429
430 // empty command list
431 if(!a_pipeline.length) {
432 exit('no commands given');
433 }
434
435 // const parse_series = a_args => mk_yargs()
436 // .command('content.ttl.read', 'read Turtle document', (yargs_read) => {
437 // return yargs_read
438 // .options({
439 // u: {
440 // type: 'boolean',
441 // default: false,
442 // alias: 'union',
443 // group: s_group_multi_input,
444 // describe: 'perform the union of multiple inputs',
445 // conflicts: a_dual_input_keys.filter(s => 'u' !== s),
446 // },
447 // i: {
448 // type: 'boolean',
449 // default: false,
450 // alias: ['intersect', 'intersection'],
451 // group: s_group_multi_input,
452 // describe: 'perform the intersection of multiple inputs',
453 // conflicts: a_dual_input_keys.filter(s => 'i' !== s),
454 // },
455 // d: {
456 // type: 'boolean',
457 // alias: ['diff', 'difference'],
458 // group: s_group_dual_input,
459 // describe: 'perform a difference between two inputs',
460 // conflicts: a_dual_input_keys.filter(s => 'd' !== s),
461 // },
462 // m: {
463 // type: 'boolean',
464 // alias: ['minus', 'subtract', 'subtraction'],
465 // group: s_group_dual_input,
466 // describe: 'perform a subtraction by removing input-B from input-A',
467 // conflicts: a_dual_input_keys.filter(s => 'm' !== s),
468 // },
469 // })
470 // .help()
471 // .parse();
472 // })
473 // .command('util.dataset.tree')
474 // .help()
475 // .parse(a_args);
476
477 (async() => {
478 // starting inputs default to stdin if no explicit inputs given
479 let a_prev = a_inputs.length? a_inputs: [process.stdin];
480
481 // each series in pipeline
482 for(let a_series of a_pipeline) {
483 // start with command string
484 let s_command = a_series[0];
485
486 // no such command
487 if(!(s_command in h_commands)) {
488 exit(`no such command '${s_command}'`);
489 }
490
491 try {
492 // eval command with its args
493 let a_curr = await h_commands[s_command](a_series.slice(1), {
494 command: s_command,
495 inputs: a_prev,
496 });
497
498 // advance inputs
499 a_prev = a_curr;
500 }
501 catch(e_parse) {
502 // console.warn(e_parse.stack);
503 return;
504 }
505 }
506
507 // expect single output
508 if(1 !== a_prev.length) {
509 exit(`expected a single output stream but last command produces ${a_prev.length} streams`);
510 }
511
512 // pipe output to stdout
513 a_prev[0].pipe(process.stdout);
514 })();
515}