Skip to content

Commit

Permalink
closes #10 add a way to get detailed error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
kof committed Jan 12, 2014
1 parent ad84754 commit 2d6fe07
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 235 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
components
.DS_Store
npm-debug.log
tmp
*~
*.swp
*.log
*.pid
*.swo
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
test:
node ./test/test.js

lint:
linter -f ./lib/cjson.js


.PHONY: test lint
.PHONY: test
229 changes: 228 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1,228 @@
module.exports = require('./lib/cjson.js');
var fs = require('fs'),
Path = require('path');

/**
* Default options.
*
* @type {Object}
*/
exports.options = {
// merge all passed/found config files, see `cjson.extend`
merge: false,
// allows you to do some string replacements, see `cjson.replace`.
replace: null,
// freeze config recursively, see `cjson.freeze`
freeze: false,
// you can use any other extension for your config files, f.e. *.cjson
ext: '.json',
// you can use any parser, f.e. if you need a more detailed error description,
// you could use "jsonlint" module
parse: JSON.parse
}

/**
* Remove single and multilie comments. Make sure to
* leave them inside of strings.
*
* @param {String} json file.
* @return {String} json without comments.
*/
exports.decomment = function(str) {
var i,
curChar, nextChar,
inString = false,
inComment = false,
newStr = '';

for (i = 0; i < str.length; ++i) {
curChar = str.charAt(i);
nextChar = str.charAt(i + 1);

// it's either closing or opening inString and it is not escaped
if (!inComment && curChar === '"' && str.charAt(i - 1) !== '\\') {
inString = !inString;
}

// we are not inside of a string
if (!inString) {
// singleline comment start
if (!inComment && curChar + nextChar === '//') {
++i;
inComment = 1;
// singleline comment end
} else if (inComment === 1 && curChar === '\n') {
inComment = false;
// multiline comment start
} else if (!inComment && curChar + nextChar === '/*') {
++i;
inComment = 2;
curChar = '';
// multiline comment end
} else if (inComment === 2 && curChar + nextChar === '*/') {
++i;
inComment = false;
curChar = '';
}

if (inComment) {
curChar = '';
}
}

newStr += curChar;
}


return newStr;
};

/**
* Decomment the string and parse json.
*
* @param {String} json.
* @param {Function} [reviver] will be called for every key and value at every
* level of the final result.
* @return {Object} parsed json object.
*/
exports.parse = function(str, reviver) {
return exports.options.parse(exports.decomment(str), reviver);
};

/**
* Replace templates with data. {{toReplace}}
*
* @param {String} json.
* @param {Object} data data hash.
* @return {String} json string with replaced data.
*/
exports.replace = function(str, data) {
return str.replace(/\{\{([^}]+)\}\}/g, function(match, search) {
return data[search] ? data[search] : match;
});
};

/**
* Merge objects to the first one
*
* @param {Boolean|Object} deep if set true, deep merge will be done.
* @param {Object} obj1 any object.
* @param {Object} obj2 any object.
* @return {Object} target merged object.
*/
exports.extend = (function() {
var toString = Object.prototype.toString,
obj = '[object Object]';

return function extend(deep, obj1, obj2 /*, obj1, obj2, obj3 */) {
// take first argument, if its not a boolean
var args = arguments,
i = deep === true ? 1 : 0,
key,
target = args[i];

for (++i; i < args.length; ++i) {
for (key in args[i]) {
if (deep === true &&
target[key] &&
// if not doing this check you may end in
// endless loop if using deep option
toString.call(args[i][key]) === obj &&
toString.call(target[key]) === obj) {

// create a copy of target object to avoid subobjects changes
target[key] = extend(deep, {}, target[key]);

extend(deep, target[key], args[i][key]);
} else {
target[key] = args[i][key];
}
}
}

return target;
};
}());

/**
* Freeze the object recursively.
*
* @param {Object} obj.
* @return {Object}
*/
exports.freeze = function freeze(obj) {
var key;

if (obj instanceof Object) {
for (key in obj) {
freeze(obj[key]);
}

Object.freeze(obj);
}
};

/**
* Load and parse a config file/files.
*
* @param {String|Array} path absolute path/paths to the file/files or dir.
* @param {Object|Boolean} [options] if true, extend all jsons to the first one,
* it can be also object {merge: true, replace: {key: 'value'}}
* @return {Object} conf parsed json object.
*/
exports.load = function load(path, options) {
var data, paths, conf;

if (options === true) {
options = {merge: true};
}

options = exports.extend({}, exports.options, options);

if (Array.isArray(path)) {
conf = {};
path.forEach(function(path) {
var data = load(path, options),
filename;

if (options.merge) {
exports.extend(true, conf, data);
} else {
filename = Path.basename(path, options.ext);
conf[filename] = data;
}
});

return conf;
}

if (fs.statSync(path).isDirectory()) {
paths = [];
fs.readdirSync(path).forEach(function(filename) {
var file = Path.join(path, filename);

if (Path.extname(file) == options.ext && fs.statSync(file).isFile()) {
paths.push(file);
}
});

return load(paths, options);
}

data = fs.readFileSync(path, 'utf-8');

if (options.replace) {
data = exports.replace(data, options.replace);
}

try {
data = exports.parse(data);
} catch(e) {
throw new Error(e.message + '"\nFile: "' + path + '"\n');
}

if (options.freeze) {
exports.freeze(data);
}

return data;
};
Loading

0 comments on commit 2d6fe07

Please sign in to comment.