diff options
Diffstat (limited to 'node_modules/json5/lib')
-rwxr-xr-x | node_modules/json5/lib/cli.js | 41 | ||||
-rw-r--r-- | node_modules/json5/lib/json5.js | 770 | ||||
-rw-r--r-- | node_modules/json5/lib/require.js | 18 |
3 files changed, 829 insertions, 0 deletions
diff --git a/node_modules/json5/lib/cli.js b/node_modules/json5/lib/cli.js new file mode 100755 index 0000000..9b72f6d --- /dev/null +++ b/node_modules/json5/lib/cli.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +// 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 USAGE = [ + 'Usage: json5 -c path/to/file.json5 ...', + 'Compiles JSON5 files into sibling JSON files with the same basenames.', +].join('\n'); + +// if valid, args look like [node, json5, -c, file1, file2, ...] +var args = process.argv; + +if (args.length < 4 || args[2] !== '-c') { + console.error(USAGE); + process.exit(1); +} + +var cwd = process.cwd(); +var files = args.slice(3); + +// 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 json5 = FS.readFileSync(path, 'utf8'); + var obj = JSON5.parse(json5); + var json = JSON.stringify(obj, null, 4); // 4 spaces; TODO configurable? + + path = Path.join(dirname, basename + '.json'); + FS.writeFileSync(path, json, 'utf8'); +}); diff --git a/node_modules/json5/lib/json5.js b/node_modules/json5/lib/json5.js new file mode 100644 index 0000000..4a4590d --- /dev/null +++ b/node_modules/json5/lib/json5.js @@ -0,0 +1,770 @@ +// json5.js +// Modern JSON. See README.md for details. +// +// This file is based directly off of Douglas Crockford's json_parse.js: +// https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js + +var JSON5 = (typeof exports === 'object' ? exports : {}); + +JSON5.parse = (function () { + "use strict"; + +// This is a function that can parse a JSON5 text, producing a JavaScript +// data structure. It is a simple, recursive descent parser. It does not use +// eval or regular expressions, so it can be used as a model for implementing +// a JSON5 parser in other languages. + +// We are defining the function inside of another function to avoid creating +// global variables. + + var at, // The index of the current character + lineNumber, // The current line number + columnNumber, // The current column number + ch, // The current character + escapee = { + "'": "'", + '"': '"', + '\\': '\\', + '/': '/', + '\n': '', // Replace escaped newlines in strings w/ empty string + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t' + }, + ws = [ + ' ', + '\t', + '\r', + '\n', + '\v', + '\f', + '\xA0', + '\uFEFF' + ], + text, + + renderChar = function (chr) { + return chr === '' ? 'EOF' : "'" + chr + "'"; + }, + + error = function (m) { + +// Call error when something is wrong. + + var error = new SyntaxError(); + // beginning of message suffix to agree with that provided by Gecko - see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse + error.message = m + " at line " + lineNumber + " column " + columnNumber + " of the JSON5 data. Still to read: " + JSON.stringify(text.substring(at - 1, at + 19)); + error.at = at; + // These two property names have been chosen to agree with the ones in Gecko, the only popular + // environment which seems to supply this info on JSON.parse + error.lineNumber = lineNumber; + error.columnNumber = columnNumber; + throw error; + }, + + next = function (c) { + +// If a c parameter is provided, verify that it matches the current character. + + if (c && c !== ch) { + error("Expected " + renderChar(c) + " instead of " + renderChar(ch)); + } + +// Get the next character. When there are no more characters, +// return the empty string. + + ch = text.charAt(at); + at++; + columnNumber++; + if (ch === '\n' || ch === '\r' && peek() !== '\n') { + lineNumber++; + columnNumber = 0; + } + return ch; + }, + + peek = function () { + +// Get the next character without consuming it or +// assigning it to the ch varaible. + + return text.charAt(at); + }, + + identifier = function () { + +// Parse an identifier. Normally, reserved words are disallowed here, but we +// only use this for unquoted object keys, where reserved words are allowed, +// so we don't check for those here. References: +// - http://es5.github.com/#x7.6 +// - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables +// - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm +// TODO Identifiers can have Unicode "letters" in them; add support for those. + + var key = ch; + + // Identifiers must start with a letter, _ or $. + if ((ch !== '_' && ch !== '$') && + (ch < 'a' || ch > 'z') && + (ch < 'A' || ch > 'Z')) { + error("Bad identifier as unquoted key"); + } + + // Subsequent characters can contain digits. + while (next() && ( + ch === '_' || ch === '$' || + (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9'))) { + key += ch; + } + + return key; + }, + + number = function () { + +// Parse a number value. + + var number, + sign = '', + string = '', + base = 10; + + if (ch === '-' || ch === '+') { + sign = ch; + next(ch); + } + + // support for Infinity (could tweak to allow other words): + if (ch === 'I') { + number = word(); + if (typeof number !== 'number' || isNaN(number)) { + error('Unexpected word for number'); + } + return (sign === '-') ? -number : number; + } + + // support for NaN + if (ch === 'N' ) { + number = word(); + if (!isNaN(number)) { + error('expected word to be NaN'); + } + // ignore sign as -NaN also is NaN + return number; + } + + if (ch === '0') { + string += ch; + next(); + if (ch === 'x' || ch === 'X') { + string += ch; + next(); + base = 16; + } else if (ch >= '0' && ch <= '9') { + error('Octal literal'); + } + } + + switch (base) { + case 10: + while (ch >= '0' && ch <= '9' ) { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { + string += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + } + break; + case 16: + while (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f') { + string += ch; + next(); + } + break; + } + + if(sign === '-') { + number = -string; + } else { + number = +string; + } + + if (!isFinite(number)) { + error("Bad number"); + } else { + return number; + } + }, + + string = function () { + +// Parse a string value. + + var hex, + i, + string = '', + delim, // double quote or single quote + uffff; + +// When parsing for string values, we must look for ' or " and \ characters. + + if (ch === '"' || ch === "'") { + delim = ch; + while (next()) { + if (ch === delim) { + next(); + return string; + } else if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (ch === '\r') { + if (peek() === '\n') { + next(); + } + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else if (ch === '\n') { + // unescaped newlines are invalid; see: + // https://github.com/aseemk/json5/issues/24 + // TODO this feels special-cased; are there other + // invalid unescaped chars? + break; + } else { + string += ch; + } + } + } + error("Bad string"); + }, + + inlineComment = function () { + +// Skip an inline comment, assuming this is one. The current character should +// be the second / character in the // pair that begins this inline comment. +// To finish the inline comment, we look for a newline or the end of the text. + + if (ch !== '/') { + error("Not an inline comment"); + } + + do { + next(); + if (ch === '\n' || ch === '\r') { + next(); + return; + } + } while (ch); + }, + + blockComment = function () { + +// Skip a block comment, assuming this is one. The current character should be +// the * character in the /* pair that begins this block comment. +// To finish the block comment, we look for an ending */ pair of characters, +// but we also watch for the end of text before the comment is terminated. + + if (ch !== '*') { + error("Not a block comment"); + } + + do { + next(); + while (ch === '*') { + next('*'); + if (ch === '/') { + next('/'); + return; + } + } + } while (ch); + + error("Unterminated block comment"); + }, + + comment = function () { + +// Skip a comment, whether inline or block-level, assuming this is one. +// Comments always begin with a / character. + + if (ch !== '/') { + error("Not a comment"); + } + + next('/'); + + if (ch === '/') { + inlineComment(); + } else if (ch === '*') { + blockComment(); + } else { + error("Unrecognized comment"); + } + }, + + white = function () { + +// Skip whitespace and comments. +// Note that we're detecting comments by only a single / character. +// This works since regular expressions are not valid JSON(5), but this will +// break if there are other valid values that begin with a / character! + + while (ch) { + if (ch === '/') { + comment(); + } else if (ws.indexOf(ch) >= 0) { + next(); + } else { + return; + } + } + }, + + word = function () { + +// true, false, or null. + + switch (ch) { + case 't': + next('t'); + next('r'); + next('u'); + next('e'); + return true; + case 'f': + next('f'); + next('a'); + next('l'); + next('s'); + next('e'); + return false; + case 'n': + next('n'); + next('u'); + next('l'); + next('l'); + return null; + case 'I': + next('I'); + next('n'); + next('f'); + next('i'); + next('n'); + next('i'); + next('t'); + next('y'); + return Infinity; + case 'N': + next( 'N' ); + next( 'a' ); + next( 'N' ); + return NaN; + } + error("Unexpected " + renderChar(ch)); + }, + + value, // Place holder for the value function. + + array = function () { + +// Parse an array value. + + var array = []; + + if (ch === '[') { + next('['); + white(); + while (ch) { + if (ch === ']') { + next(']'); + return array; // Potentially empty array + } + // ES5 allows omitting elements in arrays, e.g. [,] and + // [,null]. We don't allow this in JSON5. + if (ch === ',') { + error("Missing array element"); + } else { + array.push(value()); + } + white(); + // If there's no comma after this value, this needs to + // be the end of the array. + if (ch !== ',') { + next(']'); + return array; + } + next(','); + white(); + } + } + error("Bad array"); + }, + + object = function () { + +// Parse an object value. + + var key, + object = {}; + + if (ch === '{') { + next('{'); + white(); + while (ch) { + if (ch === '}') { + next('}'); + return object; // Potentially empty object + } + + // Keys can be unquoted. If they are, they need to be + // valid JS identifiers. + if (ch === '"' || ch === "'") { + key = string(); + } else { + key = identifier(); + } + + white(); + next(':'); + object[key] = value(); + white(); + // If there's no comma after this pair, this needs to be + // the end of the object. + if (ch !== ',') { + next('}'); + return object; + } + next(','); + white(); + } + } + error("Bad object"); + }; + + value = function () { + +// Parse a JSON value. It could be an object, an array, a string, a number, +// or a word. + + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + case "'": + return string(); + case '-': + case '+': + case '.': + return number(); + default: + return ch >= '0' && ch <= '9' ? number() : word(); + } + }; + +// Return the json_parse function. It will have access to all of the above +// functions and variables. + + return function (source, reviver) { + var result; + + text = String(source); + at = 0; + lineNumber = 1; + columnNumber = 1; + ch = ' '; + result = value(); + white(); + if (ch) { + error("Syntax error"); + } + +// If there is a reviver function, we recursively walk the new structure, +// passing each name/value pair to the reviver function for possible +// transformation, starting with a temporary root object that holds the result +// in an empty key. If there is not a reviver function, we simply return the +// result. + + return typeof reviver === 'function' ? (function walk(holder, key) { + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + }({'': result}, '')) : result; + }; +}()); + +// JSON5 stringify will not quote keys where appropriate +JSON5.stringify = function (obj, replacer, space) { + if (replacer && (typeof(replacer) !== "function" && !isArray(replacer))) { + throw new Error('Replacer must be a function or an array'); + } + var getReplacedValueOrUndefined = function(holder, key, isTopLevel) { + var value = holder[key]; + + // Replace the value with its toJSON value first, if possible + if (value && value.toJSON && typeof value.toJSON === "function") { + value = value.toJSON(); + } + + // If the user-supplied replacer if a function, call it. If it's an array, check objects' string keys for + // presence in the array (removing the key/value pair from the resulting JSON if the key is missing). + if (typeof(replacer) === "function") { + return replacer.call(holder, key, value); + } else if(replacer) { + if (isTopLevel || isArray(holder) || replacer.indexOf(key) >= 0) { + return value; + } else { + return undefined; + } + } else { + return value; + } + }; + + function isWordChar(c) { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c === '_' || c === '$'; + } + + function isWordStart(c) { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c === '_' || c === '$'; + } + + function isWord(key) { + if (typeof key !== 'string') { + return false; + } + if (!isWordStart(key[0])) { + return false; + } + var i = 1, length = key.length; + while (i < length) { + if (!isWordChar(key[i])) { + return false; + } + i++; + } + return true; + } + + // export for use in tests + JSON5.isWord = isWord; + + // polyfills + function isArray(obj) { + if (Array.isArray) { + return Array.isArray(obj); + } else { + return Object.prototype.toString.call(obj) === '[object Array]'; + } + } + + function isDate(obj) { + return Object.prototype.toString.call(obj) === '[object Date]'; + } + + var objStack = []; + function checkForCircular(obj) { + for (var i = 0; i < objStack.length; i++) { + if (objStack[i] === obj) { + throw new TypeError("Converting circular structure to JSON"); + } + } + } + + function makeIndent(str, num, noNewLine) { + if (!str) { + return ""; + } + // indentation no more than 10 chars + if (str.length > 10) { + str = str.substring(0, 10); + } + + var indent = noNewLine ? "" : "\n"; + for (var i = 0; i < num; i++) { + indent += str; + } + + return indent; + } + + var indentStr; + if (space) { + if (typeof space === "string") { + indentStr = space; + } else if (typeof space === "number" && space >= 0) { + indentStr = makeIndent(" ", space, true); + } else { + // ignore space parameter + } + } + + // Copied from Crokford's implementation of JSON + // See https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195 + // Begin + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + function escapeString(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? + c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + // End + + function internalStringify(holder, key, isTopLevel) { + var buffer, res; + + // Replace the value, if necessary + var obj_part = getReplacedValueOrUndefined(holder, key, isTopLevel); + + if (obj_part && !isDate(obj_part)) { + // unbox objects + // don't unbox dates, since will turn it into number + obj_part = obj_part.valueOf(); + } + switch(typeof obj_part) { + case "boolean": + return obj_part.toString(); + + case "number": + if (isNaN(obj_part) || !isFinite(obj_part)) { + return "null"; + } + return obj_part.toString(); + + case "string": + return escapeString(obj_part.toString()); + + case "object": + if (obj_part === null) { + return "null"; + } else if (isArray(obj_part)) { + checkForCircular(obj_part); + buffer = "["; + objStack.push(obj_part); + + for (var i = 0; i < obj_part.length; i++) { + res = internalStringify(obj_part, i, false); + buffer += makeIndent(indentStr, objStack.length); + if (res === null || typeof res === "undefined") { + buffer += "null"; + } else { + buffer += res; + } + if (i < obj_part.length-1) { + buffer += ","; + } else if (indentStr) { + buffer += "\n"; + } + } + objStack.pop(); + if (obj_part.length) { + buffer += makeIndent(indentStr, objStack.length, true) + } + buffer += "]"; + } else { + checkForCircular(obj_part); + buffer = "{"; + var nonEmpty = false; + objStack.push(obj_part); + for (var prop in obj_part) { + if (obj_part.hasOwnProperty(prop)) { + var value = internalStringify(obj_part, prop, false); + isTopLevel = false; + if (typeof value !== "undefined" && value !== null) { + buffer += makeIndent(indentStr, objStack.length); + nonEmpty = true; + key = isWord(prop) ? prop : escapeString(prop); + buffer += key + ":" + (indentStr ? ' ' : '') + value + ","; + } + } + } + objStack.pop(); + if (nonEmpty) { + buffer = buffer.substring(0, buffer.length-1) + makeIndent(indentStr, objStack.length) + "}"; + } else { + buffer = '{}'; + } + } + return buffer; + default: + // functions and undefined should be ignored + return undefined; + } + } + + // special case...when undefined is used inside of + // a compound object/array, return null. + // but when top-level, return undefined + var topLevelHolder = {"":obj}; + if (obj === undefined) { + return getReplacedValueOrUndefined(topLevelHolder, '', true); + } + return internalStringify(topLevelHolder, '', true); +}; diff --git a/node_modules/json5/lib/require.js b/node_modules/json5/lib/require.js new file mode 100644 index 0000000..73e56fe --- /dev/null +++ b/node_modules/json5/lib/require.js @@ -0,0 +1,18 @@ +// require.js +// Node.js only: adds a require() hook for .json5 files, just like the native +// hook for .json files. +// +// Usage: +// require('json5/require'); +// require('./foo'); // will check foo.json5 after foo.js, foo.json, etc. +// require('./bar.json5'); + +var FS = require('fs'); +var JSON5 = require('./json5'); + +// Modeled off of (v0.6.18 link; check latest too): +// https://github.com/joyent/node/blob/v0.6.18/lib/module.js#L468-L472 +require.extensions['.json5'] = function (module, filename) { + var content = FS.readFileSync(filename, 'utf8'); + module.exports = JSON5.parse(content); +}; |