1 | var _ = require('underscore'),
|
2 | async = require('async'),
|
3 | bower = require('bower'),
|
4 | config = require('./config'),
|
5 | fs = require('fs'),
|
6 | fu = require('./fileUtil'),
|
7 | path = require('path'),
|
8 | resources = require('./util/resources');
|
9 |
|
10 | function Libraries(options) {
|
11 | this.options = options;
|
12 | this.mixins = [];
|
13 | this.configs = [];
|
14 | }
|
15 |
|
16 | Libraries.prototype.initialize = function(context, callback) {
|
17 | this.mixins = [];
|
18 | this.originalConfig = _.clone(context.config.attributes);
|
19 |
|
20 | function normalize(libraries) {
|
21 | if (_.isString(libraries)) {
|
22 | return [libraries];
|
23 | } else {
|
24 | return _.map(libraries, function (name) {
|
25 | if (_.isString(name)) {
|
26 | return path.normalize(name);
|
27 | } else {
|
28 | return name;
|
29 | }
|
30 | });
|
31 | }
|
32 | }
|
33 |
|
34 | var commandLineLibraries = normalize(this.options.libraries || []),
|
35 | configLibraries = normalize(context.config.attributes.libraries || context.config.attributes.mixins || []),
|
36 | bowerLibraries = this.bowerLibraries(context) || [],
|
37 |
|
38 | allLibraries = _.union(commandLineLibraries, configLibraries, bowerLibraries);
|
39 |
|
40 | delete context.config.attributes.mixins;
|
41 |
|
42 | async.forEachSeries(allLibraries, _.bind(this.load, this, context), callback);
|
43 | };
|
44 |
|
45 | Libraries.prototype.bowerLibraries = function(context) {
|
46 | try {
|
47 | fs.statSync(fu.resolvePath('bower.json'));
|
48 |
|
49 | var bowerDir = bower.config.directory,
|
50 | possibleModules = fs.readdirSync(bowerDir);
|
51 |
|
52 | return possibleModules
|
53 | .map(function(name) {
|
54 | return path.normalize(path.join(bowerDir, name));
|
55 | })
|
56 | .filter(function(name) {
|
57 | try {
|
58 | fs.statSync(path.join(name, 'lumbar.json'));
|
59 | return true;
|
60 | } catch (err) {
|
61 |
|
62 | }
|
63 | });
|
64 | } catch (err) {
|
65 | context.event.emit('debug', err);
|
66 | }
|
67 | };
|
68 |
|
69 | Libraries.prototype.load = function(context, libraryConfig, callback) {
|
70 |
|
71 | var root = libraryConfig.root,
|
72 | configPath,
|
73 | self = this;
|
74 |
|
75 |
|
76 | if (!_.isObject(libraryConfig)) {
|
77 | root = root || libraryConfig;
|
78 |
|
79 |
|
80 | try {
|
81 | var stat = fs.statSync(fu.resolvePath(libraryConfig));
|
82 | if (stat.isDirectory()) {
|
83 | libraryConfig = libraryConfig + '/lumbar.json';
|
84 | } else if (root === libraryConfig) {
|
85 |
|
86 | root = path.dirname(root);
|
87 | }
|
88 | } catch (err) {
|
89 | return callback(err);
|
90 | }
|
91 |
|
92 | configPath = fu.resolvePath(libraryConfig);
|
93 | libraryConfig = config.readConfig(configPath);
|
94 | }
|
95 |
|
96 |
|
97 | if (root && root.indexOf(path.sep, root.length - 1) == -1) {
|
98 | root = root + path.sep;
|
99 | }
|
100 |
|
101 | if (!libraryConfig.name) {
|
102 | return callback(new Error('Mixin with root "' + root + '" is missing a name.'));
|
103 | }
|
104 |
|
105 | var mixins = libraryConfig.mixins,
|
106 | toRegister = {};
|
107 | delete libraryConfig.mixins;
|
108 |
|
109 | function mapMixin(mixin, name) {
|
110 |
|
111 | if (!toRegister[name]) {
|
112 | toRegister[name] = {
|
113 | serialize: function() {
|
114 | return {name: this.name, library: this.parent.name};
|
115 | },
|
116 | name: name,
|
117 | attributes: mixin,
|
118 | parent: libraryConfig,
|
119 | root: root
|
120 | };
|
121 | }
|
122 | }
|
123 |
|
124 |
|
125 | _.each(mixins, mapMixin, this);
|
126 |
|
127 |
|
128 | _.each(libraryConfig.modules, mapMixin, this);
|
129 |
|
130 |
|
131 | _.each(toRegister, function(mixin, name) {
|
132 | this.mixins[name] = this.mixins[name] || [];
|
133 | var list = this.mixins[name];
|
134 | list.push(mixin);
|
135 | }, this);
|
136 |
|
137 |
|
138 | libraryConfig.root = root;
|
139 | libraryConfig.path = configPath;
|
140 | context.loadedLibrary = libraryConfig;
|
141 | context.plugins.loadMixin(context, function(err) {
|
142 | delete libraryConfig.root;
|
143 |
|
144 |
|
145 | _.defaults(context.config.attributes, _.omit(context.loadedLibrary, 'name', 'path'));
|
146 |
|
147 | libraryConfig.serialize = function() {
|
148 | return { library: this.name };
|
149 | };
|
150 |
|
151 | libraryConfig.root = root;
|
152 | self.configs.push(libraryConfig);
|
153 |
|
154 | callback(err);
|
155 | });
|
156 | };
|
157 |
|
158 | Libraries.prototype.findDecl = function(mixins, mixinName) {
|
159 | if (!mixinName.name) {
|
160 | mixinName = {name: mixinName};
|
161 | }
|
162 |
|
163 | return _.find(mixins, function(mixinDecl) {
|
164 | return (mixinDecl.name || mixinDecl) === mixinName.name
|
165 | && (!mixinDecl.library || mixinDecl.library === mixinName.library);
|
166 | });
|
167 | };
|
168 |
|
169 | Libraries.prototype.moduleMixins = function(module) {
|
170 |
|
171 | var mixins = _.clone(module.mixins || []),
|
172 | processed = {};
|
173 | for (var i = 0; i < mixins.length; i++) {
|
174 | var firstInclude = mixins[i],
|
175 | mixinConfig = firstInclude.name && firstInclude,
|
176 | mixin = this.getMixin(firstInclude),
|
177 | added = [i, 0];
|
178 |
|
179 |
|
180 | if (mixinConfig) {
|
181 | mixinConfig = _.omit(mixinConfig, 'overrides', 'name', 'library');
|
182 | }
|
183 |
|
184 | if (!mixin) {
|
185 | throw new Error('Unable to find mixin "' + ((firstInclude && firstInclude.name) || firstInclude) + '"');
|
186 | }
|
187 |
|
188 |
|
189 | var processedName = mixin.name + '_' + (mixin.parent && mixin.parent.name);
|
190 | if (!processed[processedName]) {
|
191 | processed[processedName] = true;
|
192 |
|
193 | _.each(mixin.attributes.mixins, function(mixinInclude) {
|
194 |
|
195 | if (mixinConfig) {
|
196 | mixinInclude = mixinInclude.name ? _.clone(mixinInclude) : {name: mixinInclude};
|
197 | _.extend(mixinInclude, mixinConfig);
|
198 | }
|
199 |
|
200 |
|
201 |
|
202 | if (firstInclude.overrides) {
|
203 | mixinInclude.overrideLibrary = _.extend({root: mixin.parent.root}, firstInclude);
|
204 | } else {
|
205 | mixinInclude.overrideLibrary = mixin.parent;
|
206 | }
|
207 |
|
208 | if (!this.findDecl(mixins, mixinInclude)) {
|
209 | added.push(mixinInclude);
|
210 | }
|
211 | }, this);
|
212 | }
|
213 |
|
214 |
|
215 |
|
216 | if (added.length > 2) {
|
217 | mixins.splice.apply(mixins, added);
|
218 | i--;
|
219 | }
|
220 | }
|
221 |
|
222 |
|
223 | return _.map(mixins.reverse(), function(mixin) {
|
224 | var mixinConfig = mixin.name && mixin,
|
225 | name = mixin;
|
226 | if (mixinConfig) {
|
227 | mixinConfig = _.clone(mixinConfig);
|
228 | delete mixinConfig.library;
|
229 | delete mixinConfig.container;
|
230 | }
|
231 | mixin = _.extend(
|
232 | {},
|
233 | this.getMixin(name),
|
234 | mixinConfig);
|
235 | if (!mixin.attributes) {
|
236 | throw new Error('Mixin "' + (name.name || name) + '" is not defined.');
|
237 | }
|
238 |
|
239 |
|
240 | if (mixinConfig) {
|
241 | mixinConfig = _.clone(mixinConfig);
|
242 | delete mixinConfig.overrides;
|
243 | delete mixinConfig.name;
|
244 | }
|
245 |
|
246 | return {
|
247 | library: mixin,
|
248 | mixinConfig: mixinConfig
|
249 | };
|
250 | }, this);
|
251 | };
|
252 |
|
253 | Libraries.prototype.mapFiles = function(value, library, config) {
|
254 | var files = _.map(value, function(resource) {
|
255 | return this.mapFile(resource, library, config);
|
256 | }, this);
|
257 | files = _.filter(files, function(file) { return file; });
|
258 |
|
259 | return files;
|
260 | };
|
261 | Libraries.prototype.mapFile = function(resource, library, config) {
|
262 |
|
263 | if (_.isString(resource.library || resource.mixin)) {
|
264 | library = this.getConfig(resource.library || resource.mixin);
|
265 | if (!library) {
|
266 | throw new Error('Mixin "' + (resource.library || resource.mixin) + '" not found');
|
267 | }
|
268 | delete resource.mixin;
|
269 | }
|
270 |
|
271 | return resources.map(resource, library, config);
|
272 | };
|
273 |
|
274 | Libraries.prototype.mapPathToLibrary = function(src, library) {
|
275 | return resources.pathToLibrary(src, library);
|
276 | };
|
277 |
|
278 | Libraries.prototype.getMixin = function(name) {
|
279 | var mixins = (this.mixins && this.mixins[name.name || name]) || [],
|
280 | library = name.library || name.container;
|
281 | if (mixins.length > 1 && !library) {
|
282 | throw new Error(
|
283 | 'Duplicate mixins found for "' + (name.name || name) + '"'
|
284 | + _.map(mixins, function(mixin) {
|
285 | return ' parent: "' + mixin.parent.name + '"';
|
286 | }).join(''));
|
287 | }
|
288 |
|
289 | if (library) {
|
290 | if (name.name === undefined) {
|
291 | var found = _.find(this.configs, function(config) {
|
292 | return config.name === library;
|
293 | });
|
294 | if (!found) {
|
295 | throw new Error('Unable to find library "' + library + '"');
|
296 | }
|
297 | return found;
|
298 | }
|
299 |
|
300 | var found = _.find(mixins, function(mixin) {
|
301 | return mixin.parent.name === library;
|
302 | });
|
303 | if (found) {
|
304 | return found;
|
305 | } else {
|
306 | throw new Error('Mixin named "' + name.name + '" not found in library "' + library + '"');
|
307 | }
|
308 | } else if (mixins.length === 1) {
|
309 | return mixins[0];
|
310 | }
|
311 | };
|
312 | Libraries.prototype.getConfig = function(name) {
|
313 | return _.find(this.configs, function(config) { return config.name === name; });
|
314 | };
|
315 |
|
316 | Libraries.prototype.mergeHash = function(hashName, input, mixin, output) {
|
317 | if (mixin[hashName]) {
|
318 |
|
319 | if (!output[hashName] || output[hashName] === input[hashName]) {
|
320 | output[hashName] = _.clone(input[hashName] || {});
|
321 | }
|
322 | _.each(mixin[hashName], function(value, key) {
|
323 | if (!input[hashName] || !(key in input[hashName])) {
|
324 | output[hashName][key] = value;
|
325 | }
|
326 | });
|
327 | return true;
|
328 | }
|
329 | };
|
330 | Libraries.prototype.mergeFiles = function(fieldName, input, mixinData, output, library) {
|
331 | if (mixinData[fieldName]) {
|
332 | mixinData = _.isArray(mixinData[fieldName]) ? mixinData[fieldName] : [mixinData[fieldName]];
|
333 |
|
334 | var configData = input[fieldName] || [];
|
335 | if (!output[fieldName] || configData === output[fieldName]) {
|
336 | output[fieldName] = _.clone(configData);
|
337 | }
|
338 | if (!_.isArray(configData)) {
|
339 | configData = [configData];
|
340 | }
|
341 | if (!_.isArray(output[fieldName])) {
|
342 | output[fieldName] = [output[fieldName]];
|
343 | }
|
344 |
|
345 |
|
346 |
|
347 | _.each(mixinData, function(value) {
|
348 |
|
349 | value = (library.root || '') + value;
|
350 |
|
351 | output[fieldName].splice(
|
352 | output[fieldName].length - configData.length,
|
353 | 0,
|
354 | {src: value, library: library});
|
355 | });
|
356 |
|
357 | return true;
|
358 | }
|
359 | };
|
360 |
|
361 | module.exports = Libraries;
|