UNPKG

9.1 kBJavaScriptView Raw
1// External libraries are lazy-loaded only if these file types exist.
2var Yaml = null,
3 VisionmediaYaml = null,
4 Coffee = null,
5 Iced = null,
6 CSON = null,
7 PPARSER = null,
8 JSON5 = null,
9 TOML = null,
10 HJSON = null,
11 XML = null;
12
13// Define soft dependencies so transpilers don't include everything
14var COFFEE_2_DEP = 'coffeescript',
15 COFFEE_DEP = 'coffee-script',
16 ICED_DEP = 'iced-coffee-script',
17 JS_YAML_DEP = 'js-yaml',
18 YAML_DEP = 'yaml',
19 JSON5_DEP = 'json5',
20 HJSON_DEP = 'hjson',
21 TOML_DEP = 'toml',
22 CSON_DEP = 'cson',
23 PPARSER_DEP = 'properties',
24 XML_DEP = 'x2js',
25 TS_DEP = 'ts-node';
26
27var Parser = module.exports;
28
29Parser.parse = function(filename, content) {
30 var parserName = filename.substr(filename.lastIndexOf('.') +1); // file extension
31 if (typeof definitions[parserName] === 'function') {
32 return definitions[parserName](filename, content);
33 }
34 // TODO: decide what to do in case of a missing parser
35};
36
37Parser.xmlParser = function(filename, content) {
38 if (!XML) {
39 XML = require(XML_DEP);
40 }
41 var x2js = new XML();
42 var configObject = x2js.xml2js(content);
43 var rootKeys = Object.keys(configObject);
44 if(rootKeys.length === 1) {
45 return configObject[rootKeys[0]];
46 }
47 return configObject;
48};
49
50Parser.jsParser = function(filename, content) {
51 return require(filename);
52};
53
54Parser.tsParser = function(filename, content) {
55 if (!require.extensions['.ts']) {
56 require(TS_DEP).register({
57 lazy: true,
58 compilerOptions: {
59 allowJs: true,
60 }
61 });
62 }
63
64 // Imports config if it is exported via module.exports = ...
65 // See https://github.com/lorenwest/node-config/issues/524
66 var configObject = require(filename);
67
68 // Because of ES6 modules usage, `default` is treated as named export (like any other)
69 // Therefore config is a value of `default` key.
70 if (configObject.default) {
71 return configObject.default
72 }
73 return configObject;
74};
75
76Parser.coffeeParser = function(filename, content) {
77 // .coffee files can be loaded with either coffee-script or iced-coffee-script.
78 // Prefer iced-coffee-script, if it exists.
79 // Lazy load the appropriate extension
80 if (!Coffee) {
81 Coffee = {};
82
83 // The following enables iced-coffee-script on .coffee files, if iced-coffee-script is available.
84 // This is commented as per a decision on a pull request.
85 //try {
86 // Coffee = require('iced-coffee-script');
87 //}
88 //catch (e) {
89 // Coffee = require('coffee-script');
90 //}
91 try {
92 // Try to load coffeescript
93 Coffee = require(COFFEE_2_DEP);
94 }
95 catch (e) {
96 // If it doesn't exist, try to load it using the deprecated module name
97 Coffee = require(COFFEE_DEP);
98 }
99 // coffee-script >= 1.7.0 requires explicit registration for require() to work
100 if (Coffee.register) {
101 Coffee.register();
102 }
103 }
104 // Use the built-in parser for .coffee files with coffee-script
105 return require(filename);
106};
107
108Parser.icedParser = function(filename, content) {
109 Iced = require(ICED_DEP);
110
111 // coffee-script >= 1.7.0 requires explicit registration for require() to work
112 if (Iced.register) {
113 Iced.register();
114 }
115};
116
117Parser.yamlParser = function(filename, content) {
118 if (!Yaml && !VisionmediaYaml) {
119 // Lazy loading
120 try {
121 // Try to load the better js-yaml module
122 Yaml = require(JS_YAML_DEP);
123 }
124 catch (e) {
125 try {
126 // If it doesn't exist, load the fallback visionmedia yaml module.
127 VisionmediaYaml = require(YAML_DEP);
128 }
129 catch (e) { }
130 }
131 }
132 if (Yaml) {
133 return Yaml.load(content);
134 }
135 else if (VisionmediaYaml) {
136 // The yaml library doesn't like strings that have newlines but don't
137 // end in a newline: https://github.com/visionmedia/js-yaml/issues/issue/13
138 content += '\n';
139 return VisionmediaYaml.eval(Parser.stripYamlComments(content));
140 }
141 else {
142 console.error('No YAML parser loaded. Suggest adding js-yaml dependency to your package.json file.')
143 }
144};
145
146Parser.jsonParser = function(filename, content) {
147 try {
148 return JSON.parse(content);
149 }
150 catch (e) {
151 // All JS Style comments will begin with /, so all JSON parse errors that
152 // encountered a syntax error will complain about this character.
153 if (e.name !== 'SyntaxError' || e.message.indexOf('Unexpected token /') !== 0) {
154 throw e;
155 }
156 if (!JSON5) {
157 JSON5 = require(JSON5_DEP);
158 }
159 return JSON5.parse(content);
160 }
161};
162
163Parser.json5Parser = function(filename, content) {
164 if (!JSON5) {
165 JSON5 = require(JSON5_DEP);
166 }
167 return JSON5.parse(content);
168};
169
170Parser.hjsonParser = function(filename, content) {
171 if (!HJSON) {
172 HJSON = require(HJSON_DEP);
173 }
174 return HJSON.parse(content);
175};
176
177Parser.tomlParser = function(filename, content) {
178 if(!TOML) {
179 TOML = require(TOML_DEP);
180 }
181 return TOML.parse(content);
182};
183
184Parser.csonParser = function(filename, content) {
185 if (!CSON) {
186 CSON = require(CSON_DEP);
187 }
188 // Allow comments in CSON files
189 if (typeof CSON.parseSync === 'function') {
190 return CSON.parseSync(Parser.stripComments(content));
191 }
192 return CSON.parse(Parser.stripComments(content));
193};
194
195Parser.propertiesParser = function(filename, content) {
196 if (!PPARSER) {
197 PPARSER = require(PPARSER_DEP);
198 }
199 return PPARSER.parse(content, { namespaces: true, variables: true, sections: true });
200};
201
202/**
203 * Strip all Javascript type comments from the string.
204 *
205 * The string is usually a file loaded from the O/S, containing
206 * newlines and javascript type comments.
207 *
208 * Thanks to James Padolsey, and all who contributed to this implementation.
209 * http://james.padolsey.com/javascript/javascript-comment-removal-revisted/
210 *
211 * @protected
212 * @method stripComments
213 * @param fileStr {string} The string to strip comments from
214 * @param stringRegex {RegExp} Optional regular expression to match strings that
215 * make up the config file
216 * @return {string} The string with comments stripped.
217 */
218Parser.stripComments = function(fileStr, stringRegex) {
219 stringRegex = stringRegex || /(['"])(\\\1|.)+?\1/g;
220
221 var uid = '_' + +new Date(),
222 primitives = [],
223 primIndex = 0;
224
225 return (
226 fileStr
227
228 /* Remove strings */
229 .replace(stringRegex, function(match){
230 primitives[primIndex] = match;
231 return (uid + '') + primIndex++;
232 })
233
234 /* Remove Regexes */
235 .replace(/([^\/])(\/(?!\*|\/)(\\\/|.)+?\/[gim]{0,3})/g, function(match, $1, $2){
236 primitives[primIndex] = $2;
237 return $1 + (uid + '') + primIndex++;
238 })
239
240 /*
241 - Remove single-line comments that contain would-be multi-line delimiters
242 E.g. // Comment /* <--
243 - Remove multi-line comments that contain would be single-line delimiters
244 E.g. /* // <--
245 */
246 .replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '')
247
248 /*
249 Remove single and multi-line comments,
250 no consideration of inner-contents
251 */
252 .replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '')
253
254 /*
255 Remove multi-line comments that have a replaced ending (string/regex)
256 Greedy, so no inner strings/regexes will stop it.
257 */
258 .replace(RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '')
259
260 /* Bring back strings & regexes */
261 .replace(RegExp(uid + '(\\d+)', 'g'), function(match, n){
262 return primitives[n];
263 })
264 );
265
266};
267
268/**
269 * Strip YAML comments from the string
270 *
271 * The 2.0 yaml parser doesn't allow comment-only or blank lines. Strip them.
272 *
273 * @protected
274 * @method stripYamlComments
275 * @param fileStr {string} The string to strip comments from
276 * @return {string} The string with comments stripped.
277 */
278Parser.stripYamlComments = function(fileStr) {
279 // First replace removes comment-only lines
280 // Second replace removes blank lines
281 return fileStr.replace(/^\s*#.*/mg,'').replace(/^\s*[\n|\r]+/mg,'');
282};
283
284var order = ['js', 'ts', 'json', 'json5', 'hjson', 'toml', 'coffee', 'iced', 'yaml', 'yml', 'cson', 'properties', 'xml'];
285var definitions = {
286 coffee: Parser.coffeeParser,
287 cson: Parser.csonParser,
288 hjson: Parser.hjsonParser,
289 iced: Parser.icedParser,
290 js: Parser.jsParser,
291 json: Parser.jsonParser,
292 json5: Parser.json5Parser,
293 properties: Parser.propertiesParser,
294 toml: Parser.tomlParser,
295 ts: Parser.tsParser,
296 xml: Parser.xmlParser,
297 yaml: Parser.yamlParser,
298 yml: Parser.yamlParser,
299};
300
301Parser.getParser = function(name) {
302 return definitions[name];
303};
304
305Parser.setParser = function(name, parser) {
306 definitions[name] = parser;
307 if (order.indexOf(name) === -1) {
308 order.push(name);
309 }
310};
311
312Parser.getFilesOrder = function(name) {
313 if (name) {
314 return order.indexOf(name);
315 }
316 return order;
317};
318
319Parser.setFilesOrder = function(name, newIndex) {
320 if (Array.isArray(name)) {
321 return order = name;
322 }
323 if (typeof newIndex === 'number') {
324 var index = order.indexOf(name);
325 order.splice(newIndex, 0, name);
326 if (index > -1) {
327 order.splice(index >= newIndex ? index +1 : index, 1);
328 }
329 }
330 return order;
331};