// External libraries are lazy-loaded only if these file types exist. var Yaml = null, VisionmediaYaml = null, Coffee = null, Iced = null, CSON = null, PPARSER = null, JSON5 = null, TOML = null, HJSON = null, XML = null; // Define soft dependencies so transpilers don't include everything var COFFEE_2_DEP = 'coffeescript', COFFEE_DEP = 'coffee-script', ICED_DEP = 'iced-coffee-script', JS_YAML_DEP = 'js-yaml', YAML_DEP = 'yaml', JSON5_DEP = 'json5', HJSON_DEP = 'hjson', TOML_DEP = 'toml', CSON_DEP = 'cson', PPARSER_DEP = 'properties', XML_DEP = 'x2js', TS_DEP = 'ts-node'; var Parser = module.exports; Parser.parse = function(filename, content) { var parserName = filename.substr(filename.lastIndexOf('.') +1); // file extension if (typeof definitions[parserName] === 'function') { return definitions[parserName](filename, content); } // TODO: decide what to do in case of a missing parser }; Parser.xmlParser = function(filename, content) { if (!XML) { XML = require(XML_DEP); } var x2js = new XML(); var configObject = x2js.xml2js(content); var rootKeys = Object.keys(configObject); if(rootKeys.length === 1) { return configObject[rootKeys[0]]; } return configObject; }; Parser.jsParser = function(filename, content) { return require(filename); }; Parser.tsParser = function(filename, content) { if (!require.extensions['.ts']) { require(TS_DEP).register({ lazy: true, compilerOptions: { allowJs: true, } }); } // Imports config if it is exported via module.exports = ... // See https://github.com/lorenwest/node-config/issues/524 var configObject = require(filename); // Because of ES6 modules usage, `default` is treated as named export (like any other) // Therefore config is a value of `default` key. if (configObject.default) { return configObject.default } return configObject; }; Parser.coffeeParser = function(filename, content) { // .coffee files can be loaded with either coffee-script or iced-coffee-script. // Prefer iced-coffee-script, if it exists. // Lazy load the appropriate extension if (!Coffee) { Coffee = {}; // The following enables iced-coffee-script on .coffee files, if iced-coffee-script is available. // This is commented as per a decision on a pull request. //try { // Coffee = require('iced-coffee-script'); //} //catch (e) { // Coffee = require('coffee-script'); //} try { // Try to load coffeescript Coffee = require(COFFEE_2_DEP); } catch (e) { // If it doesn't exist, try to load it using the deprecated module name Coffee = require(COFFEE_DEP); } // coffee-script >= 1.7.0 requires explicit registration for require() to work if (Coffee.register) { Coffee.register(); } } // Use the built-in parser for .coffee files with coffee-script return require(filename); }; Parser.icedParser = function(filename, content) { Iced = require(ICED_DEP); // coffee-script >= 1.7.0 requires explicit registration for require() to work if (Iced.register) { Iced.register(); } }; Parser.yamlParser = function(filename, content) { if (!Yaml && !VisionmediaYaml) { // Lazy loading try { // Try to load the better js-yaml module Yaml = require(JS_YAML_DEP); } catch (e) { try { // If it doesn't exist, load the fallback visionmedia yaml module. VisionmediaYaml = require(YAML_DEP); } catch (e) { } } } if (Yaml) { return Yaml.load(content); } else if (VisionmediaYaml) { // The yaml library doesn't like strings that have newlines but don't // end in a newline: https://github.com/visionmedia/js-yaml/issues/issue/13 content += '\n'; return VisionmediaYaml.eval(Parser.stripYamlComments(content)); } else { console.error('No YAML parser loaded. Suggest adding js-yaml dependency to your package.json file.') } }; Parser.jsonParser = function(filename, content) { try { return JSON.parse(content); } catch (e) { // All JS Style comments will begin with /, so all JSON parse errors that // encountered a syntax error will complain about this character. if (e.name !== 'SyntaxError' || e.message.indexOf('Unexpected token /') !== 0) { throw e; } if (!JSON5) { JSON5 = require(JSON5_DEP); } return JSON5.parse(content); } }; Parser.json5Parser = function(filename, content) { if (!JSON5) { JSON5 = require(JSON5_DEP); } return JSON5.parse(content); }; Parser.hjsonParser = function(filename, content) { if (!HJSON) { HJSON = require(HJSON_DEP); } return HJSON.parse(content); }; Parser.tomlParser = function(filename, content) { if(!TOML) { TOML = require(TOML_DEP); } return TOML.parse(content); }; Parser.csonParser = function(filename, content) { if (!CSON) { CSON = require(CSON_DEP); } // Allow comments in CSON files if (typeof CSON.parseSync === 'function') { return CSON.parseSync(Parser.stripComments(content)); } return CSON.parse(Parser.stripComments(content)); }; Parser.propertiesParser = function(filename, content) { if (!PPARSER) { PPARSER = require(PPARSER_DEP); } return PPARSER.parse(content, { namespaces: true, variables: true, sections: true }); }; /** * Strip all Javascript type comments from the string. * * The string is usually a file loaded from the O/S, containing * newlines and javascript type comments. * * Thanks to James Padolsey, and all who contributed to this implementation. * http://james.padolsey.com/javascript/javascript-comment-removal-revisted/ * * @protected * @method stripComments * @param fileStr {string} The string to strip comments from * @param stringRegex {RegExp} Optional regular expression to match strings that * make up the config file * @return {string} The string with comments stripped. */ Parser.stripComments = function(fileStr, stringRegex) { stringRegex = stringRegex || /(['"])(\\\1|.)+?\1/g; var uid = '_' + +new Date(), primitives = [], primIndex = 0; return ( fileStr /* Remove strings */ .replace(stringRegex, function(match){ primitives[primIndex] = match; return (uid + '') + primIndex++; }) /* Remove Regexes */ .replace(/([^\/])(\/(?!\*|\/)(\\\/|.)+?\/[gim]{0,3})/g, function(match, $1, $2){ primitives[primIndex] = $2; return $1 + (uid + '') + primIndex++; }) /* - Remove single-line comments that contain would-be multi-line delimiters E.g. // Comment /* <-- - Remove multi-line comments that contain would be single-line delimiters E.g. /* // <-- */ .replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '') /* Remove single and multi-line comments, no consideration of inner-contents */ .replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '') /* Remove multi-line comments that have a replaced ending (string/regex) Greedy, so no inner strings/regexes will stop it. */ .replace(RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '') /* Bring back strings & regexes */ .replace(RegExp(uid + '(\\d+)', 'g'), function(match, n){ return primitives[n]; }) ); }; /** * Strip YAML comments from the string * * The 2.0 yaml parser doesn't allow comment-only or blank lines. Strip them. * * @protected * @method stripYamlComments * @param fileStr {string} The string to strip comments from * @return {string} The string with comments stripped. */ Parser.stripYamlComments = function(fileStr) { // First replace removes comment-only lines // Second replace removes blank lines return fileStr.replace(/^\s*#.*/mg,'').replace(/^\s*[\n|\r]+/mg,''); }; /** * Parses the environment variable to the boolean equivalent. * Defaults to false * * @param {String} content - Environment variable value * @return {boolean} - Boolean value fo the passed variable value */ Parser.booleanParser = function(filename, content) { return content === 'true'; }; /** * Parses the environment variable to the number equivalent. * Defaults to undefined * * @param {String} content - Environment variable value * @return {Number} - Number value fo the passed variable value */ Parser.numberParser = function(filename, content) { const numberValue = Number(content); return Number.isNaN(numberValue) ? undefined : numberValue; }; var order = ['js', 'cjs', 'ts', 'json', 'json5', 'hjson', 'toml', 'coffee', 'iced', 'yaml', 'yml', 'cson', 'properties', 'xml', 'boolean', 'number']; var definitions = { cjs: Parser.jsParser, coffee: Parser.coffeeParser, cson: Parser.csonParser, hjson: Parser.hjsonParser, iced: Parser.icedParser, js: Parser.jsParser, json: Parser.jsonParser, json5: Parser.json5Parser, properties: Parser.propertiesParser, toml: Parser.tomlParser, ts: Parser.tsParser, xml: Parser.xmlParser, yaml: Parser.yamlParser, yml: Parser.yamlParser, boolean: Parser.booleanParser, number: Parser.numberParser }; Parser.getParser = function(name) { return definitions[name]; }; Parser.setParser = function(name, parser) { definitions[name] = parser; if (order.indexOf(name) === -1) { order.push(name); } }; Parser.getFilesOrder = function(name) { if (name) { return order.indexOf(name); } return order; }; Parser.setFilesOrder = function(name, newIndex) { if (Array.isArray(name)) { return order = name; } if (typeof newIndex === 'number') { var index = order.indexOf(name); order.splice(newIndex, 0, name); if (index > -1) { order.splice(index >= newIndex ? index +1 : index, 1); } } return order; };