diff --git a/lib/cli.js b/lib/cli.js index 9b72f6d1..8f18fb09 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -2,40 +2,81 @@ // cli.js // JSON5 command-line interface. -// -// This is pretty minimal for now; just supports compiling files via `-c`. -// TODO More useful functionality, like output path, watch, etc.? var FS = require('fs'); var JSON5 = require('./json5'); var Path = require('path'); +var Transform = require('stream').Transform; +var streams = require('./streams'); +var yargs = require('yargs') + .help('h').alias('h', 'help') + .option('c', { + alias: 'compile', + describe: 'Compiles JSON5 files into sibling JSON files with the same basenames.', + type: 'boolean' + }) + .option('stdin', { + describe: 'read JSON5 from STDIN, write JSON to STDOUT', + type: 'boolean' + }) + .option('indent', { + describe: 'intentation level for pretty-printing JSON', + default: 4 + }) + .option('o', { + describe: 'optimize', + type: 'boolean', + }) + .strict() + .check(function(argv, opts) { + if (argv.c && argv.stdin) { + throw new Error('conflicting options: -c and --stdin'); + } -var USAGE = [ - 'Usage: json5 -c path/to/file.json5 ...', - 'Compiles JSON5 files into sibling JSON files with the same basenames.', -].join('\n'); + if (argv.c && argv._.length === 0) { + throw new Error('-c set, but no JSON5 files given'); + } -// if valid, args look like [node, json5, -c, file1, file2, ...] -var args = process.argv; + if (!argv.c && !argv.stdin) { + throw new Error('not enough options'); + } -if (args.length < 4 || args[2] !== '-c') { - console.error(USAGE); - process.exit(1); -} + return true; + }); + +var argv = yargs.argv; + +function compileFiles(files, indent) { + var cwd = process.cwd(); + + // iterate over each file and convert JSON5 files to JSON: + files.forEach(function (file) { + var path = Path.resolve(cwd, file); + var basename = Path.basename(path, '.json5'); + var dirname = Path.dirname(path); -var cwd = process.cwd(); -var files = args.slice(3); + var json5 = FS.readFileSync(path, 'utf8'); + var obj = JSON5.parse(json5); + var json = JSON.stringify(obj, null, indent) + (indent ? "\n" : ''); -// iterate over each file and convert JSON5 files to JSON: -files.forEach(function (file) { - var path = Path.resolve(cwd, file); - var basename = Path.basename(path, '.json5'); - var dirname = Path.dirname(path); + path = Path.join(dirname, basename + '.json'); + FS.writeFileSync(path, json, 'utf8'); + }); +} + +function streamInOut(input, output, indent) { + var splitIntoDocs = new streams.CharacterGateStream('}]'); + var transcoder = new streams.JSON5toJSONstream(); + input.pipe(splitIntoDocs) + // .pipe(streams.LoggerStream('split')) + .pipe(transcoder) + // .pipe(streams.LoggerStream('json')) + .pipe(output); +} - var json5 = FS.readFileSync(path, 'utf8'); - var obj = JSON5.parse(json5); - var json = JSON.stringify(obj, null, 4); // 4 spaces; TODO configurable? +function main() { + if (argv.c) return compileFiles(argv._, argv.indent); + if (argv.stdin) return streamInOut(process.stdin, process.stdout, argv.indent); +} - path = Path.join(dirname, basename + '.json'); - FS.writeFileSync(path, json, 'utf8'); -}); +main(); diff --git a/lib/streams.js b/lib/streams.js new file mode 100644 index 00000000..b013018b --- /dev/null +++ b/lib/streams.js @@ -0,0 +1,111 @@ +var JSON5 = require('./json5'); +var Transform = require('stream').Transform; +var inherits = require('util').inherits; + +/** + * This stream tries to parse its input as JSON5. + * If it succeeds, it emits the resulting object as a JSON string. + * Hint: feed on one character at a time to ensure best results. + */ +function TryJSON5toJSONstream(indents) { + Transform.call(this, {encoding: 'utf8'}); + + this._buffer = ''; + this._indents = indents === undefined ? 4 : indents; +} + +inherits(TryJSON5toJSONstream, Transform); + +TryJSON5toJSONstream.prototype._transform = function(chunk, encoding, callback) { + this._buffer += chunk; + var value; + try { + value = JSON5.parse(this._buffer); + this._buffer = ''; + } catch (err) { + callback(); + return; + } + this.push(this._stringify(value)); + callback(); +}; + +TryJSON5toJSONstream.prototype._flush = function(callback) { + var data = this._buffer.trim(); + var value; + if (data) { + try { + value = JSON5.parse(data); + } catch (err) { + this.emit('error', err); + return; + } + this.push(this._stringify(value)); + } + + callback(); +}; + +TryJSON5toJSONstream.prototype._stringify = function stringify(obj) { + return JSON.stringify(obj, null, this._indents) + (this._indents ? "\n" : ''); +}; + + +/** + * CharacterGateStream + * Emits chunks of characters delimited some values. + * Useful for splitting up input streams of JSON5 into bite-sized portions for + * the parser. + */ + +function CharacterGateStream(chars) { + Transform.call(this, {encoding: 'utf8'}); + this._chars = chars; + this._buffer = ''; +} + +inherits(CharacterGateStream, Transform); + +CharacterGateStream.prototype._transform = function(chunk, _, callback) { + chunk = '' + chunk; + for (var i=0; i -1) { + this._buffer += chunk.slice(0, i + 1); + this.push(this._buffer); + this._buffer = ''; + + // reset to begining of remaining chunk + chunk = chunk.slice(i + 1); + i = 0; + } + } + // no matches in remaining chars so add them to buff anyways + this._buffer += chunk; + callback(); +}; + +CharacterGateStream.prototype._flush = function(callback) { + if (this._buffer) this.push(this._buffer); + callback(); +}; + + +/** + * a utility function to create a transform that logs everything it sees. + */ +function logger(title) { + var stream = new Transform({encoding: 'utf8'}); + stream._transform = function(chunk, enc, cb) { + console.log(title, '' + chunk); + this.push(chunk); + cb(); + }; + return stream; +} + + +module.exports = { + JSON5toJSONstream: TryJSON5toJSONstream, + CharacterGateStream: CharacterGateStream, + logger: logger +}; diff --git a/package.json b/package.json index 81b62724..3806e3ca 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ ], "main": "lib/json5.js", "bin": "lib/cli.js", - "dependencies": {}, + "dependencies": { + "yargs": "^3.32.0" + }, "devDependencies": { "gulp": "^3.9.0", "gulp-jshint": "^1.11.2", diff --git a/package.json5 b/package.json5 index 45742dfc..1fcd30e8 100644 --- a/package.json5 +++ b/package.json5 @@ -16,7 +16,9 @@ ], main: 'lib/json5.js', bin: 'lib/cli.js', - dependencies: {}, + dependencies: { + yargs: '^3.32.0', + }, devDependencies: { gulp: '^3.9.0', 'gulp-jshint': '^1.11.2',