1 | | /*! |
2 | | * |
3 | | * Copyright (c) 2013 Sebastian Golasch |
4 | | * |
5 | | * Permission is hereby granted, free of charge, to any person obtaining a |
6 | | * copy of this software and associated documentation files (the "Software"), |
7 | | * to deal in the Software without restriction, including without limitation |
8 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
9 | | * and/or sell copies of the Software, and to permit persons to whom the |
10 | | * Software is furnished to do so, subject to the following conditions: |
11 | | * |
12 | | * The above copyright notice and this permission notice shall be included |
13 | | * in all copies or substantial portions of the Software. |
14 | | * |
15 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
16 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
20 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
21 | | * DEALINGS IN THE SOFTWARE. |
22 | | * |
23 | | */ |
24 | | |
25 | 1 | 'use strict'; |
26 | | |
27 | | // ext. libs |
28 | 1 | var path = require('path'); |
29 | 1 | var fs = require('fs'); |
30 | 1 | var _ = require('lodash'); |
31 | 1 | var yaml = require('js-yaml'); |
32 | 1 | var JSON5 = require('json5'); |
33 | 1 | require('coffee-script'); |
34 | | |
35 | | /** |
36 | | * Configures the config instance |
37 | | * |
38 | | * @param {object} defaults Default parameter options |
39 | | * @param {object} opts Command line options |
40 | | * @constructor |
41 | | */ |
42 | | |
43 | 1 | var Config = function (defaults, opts, advOpts) { |
44 | 13 | this.customFilename = null; |
45 | 13 | this.defaultFilename = 'Dalekfile'; |
46 | 13 | this.supportedExtensions = ['yml', 'json5', 'json', 'js', 'coffee']; |
47 | 13 | this.advancedOptions = advOpts; |
48 | 13 | this.config = this.load(defaults, opts.config, opts); |
49 | | }; |
50 | | |
51 | | /** |
52 | | * Parses config data & loads config files |
53 | | * |
54 | | * @module DalekJS |
55 | | * @class Config |
56 | | * @namespace Dalek |
57 | | * @part Config |
58 | | * @api |
59 | | */ |
60 | | |
61 | 1 | Config.prototype = { |
62 | | |
63 | | /** |
64 | | * Checks if a config file is available |
65 | | * |
66 | | * @method checkAvailabilityOfConfigFile |
67 | | * @param {String} pathname |
68 | | * @return {String} config File path |
69 | | */ |
70 | | |
71 | | checkAvailabilityOfConfigFile: function (pathname) { |
72 | | // check if a pathname is given, |
73 | | // then check if the file is available |
74 | 14 | if (pathname && fs.existsSync(pathname)) { |
75 | 1 | return fs.realpathSync(pathname); |
76 | | } |
77 | | |
78 | | // check if any of the default configuration files is available |
79 | 13 | return this.supportedExtensions.reduce(this._checkFile.bind(this)); |
80 | | }, |
81 | | |
82 | | /** |
83 | | * Iterator function that checks the existance of a given file |
84 | | * |
85 | | * @method _checkFile |
86 | | * @param {String} previousValue Last iterations result |
87 | | * @param {String} ext File extension to check |
88 | | * @param {integer} idx Iteration index |
89 | | * @param {object} data File data |
90 | | * @return {String} config File path |
91 | | * @private |
92 | | */ |
93 | | |
94 | | _checkFile: function (previousValue, ext, idx, data) { |
95 | 55 | if (previousValue.length > 6) { |
96 | 1 | return previousValue; |
97 | | } |
98 | | |
99 | 54 | var fileToCheck = this.defaultFilename + '.' + previousValue; |
100 | 54 | if (fs.existsSync(fileToCheck)) { |
101 | 1 | return fs.realpathSync(fileToCheck); |
102 | | } |
103 | | |
104 | 53 | return this._checkDefaultFile(ext, data); |
105 | | }, |
106 | | |
107 | | /** |
108 | | * Iterator function that checks the existance of a the default file |
109 | | * |
110 | | * @method _checkDefaultFile |
111 | | * @param {String} ext File extension to check |
112 | | * @param {object} data File data |
113 | | * @return {String} config File path |
114 | | * @private |
115 | | */ |
116 | | |
117 | | _checkDefaultFile: function (ext, data) { |
118 | 53 | if (ext === data[data.length - 1]) { |
119 | 14 | var fileToCheck = this.defaultFilename + '.' + ext; |
120 | 14 | if (fs.existsSync(fileToCheck)) { |
121 | 1 | return fs.realpathSync(fileToCheck); |
122 | | } |
123 | | } |
124 | | |
125 | 52 | return ext; |
126 | | }, |
127 | | |
128 | | /** |
129 | | * Loads a file & merges the results with the |
130 | | * commandline options & the default config |
131 | | * |
132 | | * @method load |
133 | | * @param {object} defaults Default config |
134 | | * @param {String} pathname Filename of the config file to load |
135 | | * @param {object} opts Command line options |
136 | | * @return {object} config Merged config data |
137 | | */ |
138 | | |
139 | | load: function (defaults, pathname, opts) { |
140 | 13 | var file = this.checkAvailabilityOfConfigFile(pathname); |
141 | 13 | var data = {}; |
142 | | |
143 | 13 | if (!this.advancedOptions || this.advancedOptions.dalekfile !== false) { |
144 | 13 | data = this.loadFile(file); |
145 | | } |
146 | | |
147 | | // remove the tests property if the array length is 0 |
148 | 13 | if (opts.tests.length === 0) { |
149 | 13 | delete opts.tests; |
150 | | } |
151 | | |
152 | 13 | return _.merge(defaults, data, opts, (this.advancedOptions || {})); |
153 | | }, |
154 | | |
155 | | /** |
156 | | * Loads a config file & parses it based on the file extension |
157 | | * |
158 | | * @method loadFile |
159 | | * @param {String} pathname Filename of the config file to load |
160 | | * @return {object} data Config data |
161 | | */ |
162 | | |
163 | | loadFile: function (pathname) { |
164 | 13 | var ext = path.extname(pathname).replace('.', ''); |
165 | 13 | return this['read' + ext] ? this['read' + ext](pathname) : {}; |
166 | | }, |
167 | | |
168 | | /** |
169 | | * Fetches & returns a config item |
170 | | * |
171 | | * @method get |
172 | | * @param {String} item Key of the item to load |
173 | | * @return {mixed|null} data Requested config data |
174 | | */ |
175 | | |
176 | | get: function (item) { |
177 | 1 | return this.config[item] || null; |
178 | | }, |
179 | | |
180 | | /** |
181 | | * Loads a json config file |
182 | | * |
183 | | * @method readjson |
184 | | * @return {object} data Parsed config data |
185 | | */ |
186 | | |
187 | | readjson: function (pathname) { |
188 | 1 | var contents = fs.readFileSync((pathname || this.defaultFilename + '.json'), 'utf8'); |
189 | 1 | return JSON.parse(contents); |
190 | | }, |
191 | | |
192 | | /** |
193 | | * Loads a json5 config file |
194 | | * |
195 | | * @method readJson5 |
196 | | * @return {object} data Parsed config data |
197 | | */ |
198 | | |
199 | | readjson5: function (pathname) { |
200 | 1 | var contents = fs.readFileSync((pathname || this.defaultFilename + '.json5'), 'utf8'); |
201 | 1 | return JSON5.parse(contents); |
202 | | }, |
203 | | |
204 | | /** |
205 | | * Loads a yaml config file |
206 | | * |
207 | | * @method readyaml |
208 | | * @return {object} data Parsed config data |
209 | | */ |
210 | | |
211 | | readyml: function (pathname) { |
212 | 1 | var contents = fs.readFileSync((pathname || this.defaultFilename + '.yml'), 'utf8'); |
213 | 1 | return yaml.load(contents); |
214 | | }, |
215 | | |
216 | | /** |
217 | | * Loads a javascript config file |
218 | | * |
219 | | * @method readjs |
220 | | * @return {object} data Parsed config data |
221 | | */ |
222 | | |
223 | | readjs: function (pathname) { |
224 | 1 | return require((pathname || this.defaultFilename)); |
225 | | }, |
226 | | |
227 | | /** |
228 | | * Loads a coffescript config file |
229 | | * |
230 | | * @method readcoffee |
231 | | * @return {object} data Parsed config data |
232 | | */ |
233 | | |
234 | | readcoffee: function (pathname) { |
235 | 1 | return require((pathname || this.defaultFilename)); |
236 | | }, |
237 | | |
238 | | /** |
239 | | * Verifies if a reporter is given, exists & is valid |
240 | | * |
241 | | * @method verifyReporters |
242 | | * @return {array} data List of verified reporters |
243 | | */ |
244 | | |
245 | | verifyReporters: function (reporters, reporter) { |
246 | 1 | return _.compact(this._verify(reporters, 'isReporter', reporter)); |
247 | | }, |
248 | | |
249 | | /** |
250 | | * Verifies if a driver is given, exists & is valid |
251 | | * |
252 | | * @method verifyDrivers |
253 | | * @return {array} data List of verified drivers |
254 | | */ |
255 | | |
256 | | verifyDrivers: function (drivers, driver) { |
257 | 1 | return _.compact(this._verify(drivers, 'isDriver', driver)); |
258 | | }, |
259 | | |
260 | | /** |
261 | | * Verifies if a driver is given, exists & is valid |
262 | | * |
263 | | * @method _verify |
264 | | * @param {array} check Data that shoudl be mapped |
265 | | * @param {string} fn Name of the function that should be invoked on the veryify object |
266 | | * @param {object} instance Object instance where the verify function should be invoked |
267 | | * @return {array} data List of verified items |
268 | | * @private |
269 | | */ |
270 | | |
271 | | _verify: function (check, fn, instance) { |
272 | 2 | return check.map(this._verifyIterator.bind(this, fn, instance)); |
273 | | }, |
274 | | |
275 | | /** |
276 | | * Verifies if a driver is given, exists & is valid |
277 | | * |
278 | | * @method _verifyIterator |
279 | | * @param {string} fn Name of the function that should be invoked on the veryify object |
280 | | * @param {object} instance Object instance where the verify function should be invoked |
281 | | * @param {string} elm Name of the element that should be checked |
282 | | * @return {string|null} element name of the verified element or false if checked failed |
283 | | * @priavte |
284 | | */ |
285 | | |
286 | | _verifyIterator: function (fn, instance, elm) { |
287 | 2 | return instance[fn](elm) ? elm : false; |
288 | | } |
289 | | }; |
290 | | |
291 | | // export the module |
292 | 1 | module.exports = Config; |
293 | | |