Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve command-line tool with options and streaming mode #108

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 67 additions & 26 deletions lib/cli.js
Expand Up @@ -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();
111 changes: 111 additions & 0 deletions 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<chunk.length; i++) {
if (this._chars.indexOf(chunk[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
};
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion package.json5
Expand Up @@ -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',
Expand Down