UNPKG

print-gtfs-rt-cli

Version:

Read a GTFS Realtime feed from stdin, print human-readable or as JSON.

162 lines (144 loc) 4.15 kB
#!/usr/bin/env node 'use strict' const {parseArgs} = require('util') const {isatty} = require('tty') const pkg = require('./package.json') const { values: flags, } = parseArgs({ options: { help: { type: 'boolean', short: 'h', }, version: { type: 'boolean', short: 'v', }, 'length-prefixed': { type: 'boolean', short: 'l', }, json: { type: 'boolean', short: 'j', }, 'single-json': { type: 'boolean', short: 's', }, 'depth': { type: 'string', short: 'd', }, 'include-all': { type: 'boolean', short: 'a', }, 'gtfs-rt-bindings': { type: 'string', }, }, }) if (flags.help) { process.stdout.write(` Usage: cat gtfs-rt-feed.pbf | print-gtfs-rt Options: --length-prefixed -l Read input as length-prefixed. See https://www.npmjs.com/package/length-prefixed-stream --json -j Output newline-delimeted JSON (http://ndjson.org). --single-json -s Output a single JSON array. --depth -d Number of nested levels to print. Default: infinite --include-all -a Print the entire FeedMessage, including its header. --gtfs-rt-bindings Path to GTFS-RT bindings. Must be compatible with those generated by protobufjs. Examples: curl 'https://example.org/gtfs-rt.pbf' | print-gtfs-rt \n`) process.exit(0) } if (flags.version) { process.stdout.write(`print-gtfs-rt v${pkg.version}\n`) process.exit(0) } const showError = (err) => { if (!err) return; if (err.code === 'EPIPE') return; // todo: refine this if (process.env.NODE_ENV === 'dev') console.error(err) else console.error(err.message || (err + '')) process.exit(1) } if (isatty(process.stdin.fd)) showError('You must pipe into print-gtfs-rt.') const {decode: decodeLengthPrefixed} = require('length-prefixed-stream') const {pipeline} = require('stream') const {resolve: pathResolve} = require('path') const defaultBindings = require('gtfs-rt-bindings') const {inspect} = require('util') const read = (readable) => { return new Promise((resolve, reject) => { const chunks = [] readable .once('error', reject) .on('data', chunk => chunks.push(chunk)) .once('end', () => resolve(chunks)) }) } const isLengthPrefixed = flags['length-prefixed'] const printAsNDJSON = flags.json const printAsJSON = flags['single-json'] const includeAll = flags['include-all'] const printWithColors = isatty(process.stdout.fd) const depth = flags.depth ? parseInt(flags.depth) : null const bindings = flags['gtfs-rt-bindings'] ? require(pathResolve(process.cwd(), flags['gtfs-rt-bindings'])) : defaultBindings const {FeedMessage} = bindings.transit_realtime || bindings const onFeedMessage = (buf) => { const data = FeedMessage.toObject(FeedMessage.decode(buf)) if (!data) throw new Error('invalid feed') if (!data.header) throw new Error('invalid feed: missing header') // Protocol buffers don't encode empty arrays, so .entity is missing with 0 FeedEntitys. if (!('entity' in data)) { data.entity = [] } else if (!Array.isArray(data.entity)) { throw new Error('invalid feed: missing entity[]') } const inspectOptions = {depth, colors: printWithColors}; if (includeAll) { const msg = printAsNDJSON || printAsJSON ? JSON.stringify(data) + '\n' : inspect(data, inspectOptions); process.stdout.write(msg); return; } if (printAsJSON) { process.stdout.write('[\n') } for (var i = 0; i < data.entity.length; ++i) { const entity = data.entity[i]; const msg = printAsNDJSON || printAsJSON ? JSON.stringify(entity) : inspect(entity, inspectOptions) const isLastEntity = i == data.entity.length - 1; const delimeter = (printAsJSON && !isLastEntity) ? ',\n' : '\n' process.stdout.write(msg + delimeter) } if (printAsJSON) { process.stdout.write(']\n') } } if (isLengthPrefixed) { const decoder = decodeLengthPrefixed() pipeline( process.stdin, decoder, showError, ) decoder.on('data', onFeedMessage) } else { read(process.stdin) .then(chunks => onFeedMessage(Buffer.concat(chunks))) .catch(showError) } process.stdout.on('error', showError)