Running "mochacov:cov" (mochacov) task Coverage

Coverage

88%
2074
1836
238

/Users/kpdecker/dev/walmart/lumbar/lib/build.js

91%
48
44
4
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 fu = require('./fileUtil');
4
5/**
6 * Creates a list of all of the resources for the current module.
7 *
8 * Context state: module
9 *
10 * Plugin Calls:
11 * moduleResources
12 * fileFilter
13 * resourceList
14 */
151exports.loadResources = function(context, callback) {
16311 var plugins = context.plugins;
17
18311 function filterResource(resource) {
19535 if (_.isString(resource)) {
20161 resource = { src: resource };
21 }
22
23535 if (exports.filterResource(resource, context)) {
24454 return resource;
25 }
26 }
27
28311 plugins.moduleResources(context, function(err, files) {
29311 if (err) {
300 return callback(err);
31 }
32
33311 var fileFilter = plugins.fileFilter(context) || /.*/;
34311 fu.fileList(files, fileFilter, function(err, files) {
35311 if (err) {
360 callback(err);
370 return;
38 }
39
40311 async.map(files, function(resource, callback) {
41488 var resourceContext = context.clone();
42488 resourceContext.resource = resource;
43488 plugins.resourceList(resourceContext, callback);
44 },
45 function(err, resources) {
46311 resources = _.flatten(resources);
47311 resources = _.map(resources, filterResource);
48846 resources = _.filter(resources, function(resource) { return resource; });
49311 callback(err, resources);
50 });
51 });
52 });
53};
54
55/**
56 * Filters a given resource for platform constraints, if specified.
57 */
581exports.filterResource = function(resource, context) {
59875 function check(value, singular, plural) {
602377 if (typeof singular !== 'undefined') {
61172 return singular.not ? singular.not !== value : singular === value;
622205 } else if (plural) {
6373 var ret = (plural.not || plural).reduce(function(found, filePlatform) {
64105 return found || filePlatform === value;
65 }, false);
6673 return plural.not ? !ret : ret;
67 }
682132 return true;
69 }
70
71875 function checkResource(resource) {
72879 return check(context.platform, resource.platform, resource.platforms)
73 && check(context.package, resource.package, resource.packages)
74 && check(!!context.combined, resource.combined);
75 }
76875 return checkResource(resource)
77 && (!resource.originalResource || checkResource(resource.originalResource));
78};
79
80
81/**
82 * Runs a set of resources through the resource plugin.
83 *
84 * Context state: module
85 *
86 * Plugin Calls:
87 * resource
88 */
891exports.processResources = function(resources, context, callback) {
90313 var plugins = context.plugins;
91
92313 async.map(resources, function(resource, callback) {
93432 var resourceContext = context.clone();
94432 resourceContext.resource = resource;
95432 plugins.resource(resourceContext, function(err, newResource) {
96432 if (newResource && newResource !== resource) {
9797 newResource.originalResource = resource;
98 }
99
100432 callback(err, newResource);
101 });
102 },
103 function(err, resources) {
104313 if (err) {
1050 return callback(err);
106 }
107
108745 callback(err, resources.filter(function(resource) { return resource; }));
109 });
110};
111

/Users/kpdecker/dev/walmart/lumbar/lib/child-pool.js

87%
39
34
5
LineHitsSource
11var _ = require('underscore'),
2 cp = require('child_process');
3
41var numCPUs = require('os').cpus().length;
5
61exports = module.exports = function(module, options) {
72 this.module = module;
82 this.options = _.defaults({workers: numCPUs, keepAlive: 100}, options);
92 this.workers = [];
102 this.queue = [];
112 this.cullTimeout = undefined;
12};
131exports.prototype.sendAll = function(message) {
14233 _.each(this.workers, function(worker) {
15455 worker.process.send(message);
16 });
17};
181exports.prototype.send = function(message, callback) {
1931 var self = this;
20
2131 callback = callback || function() {};
22
2331 function fork() {
2412 worker = {
25 callback: false,
26 process: cp.fork(self.module)
27 };
2812 worker.process.on('message', function(msg) {
2931 if (msg.log) {
300 return console.log(msg.log);
31 }
32
3331 var callback = worker.callback;
3431 worker.callback = undefined;
35
3631 if (self.queue.length) {
370 var queued = self.queue.shift();
380 self.send(queued.message, queued.callback);
39 } else {
40 // Check for process culling
4131 worker.cullTimeout = setTimeout(function() {
42 // Kill
4312 worker.process.kill();
4412 self.workers = _.without(self.workers, worker);
45 }, self.options.keepAlive);
46 }
47
48 // Dispatch
4931 process.nextTick(function() {
5031 callback(msg.err, msg.data);
51 });
52 });
5312 self.workers.push(worker);
54 }
55
5685 var worker = _.find(this.workers, function(worker) { return !worker.callback; });
5731 if (!worker) {
5812 if (this.workers.length < this.options.workers) {
59 // Fork!
6012 fork();
61 } else {
62 // Queue!
630 self.queue.push({message: message, callback: callback});
640 return;
65 }
66 }
67
6831 clearTimeout(worker.cullTimeout);
6931 worker.callback = callback;
7031 worker.process.send(message);
71};
72

/Users/kpdecker/dev/walmart/lumbar/lib/config.js

85%
67
57
10
LineHitsSource
11var _ = require('underscore'),
2 fu = require('./fileUtil'),
3 path = require('path'),
4 vm = require('vm');
5
6/**
7 * Reads the RAW JSON for a lumbar config file.
8 */
91exports.readConfig = function(lumbarFile) {
1033 try {
1133 var data = '(' + fu.readFileSync(lumbarFile) + ')';
12
13 // Yes this is totally unsafe, but we don't want the strictness of pure JSON for our
14 // config files and if you are running an untrusted lumbar file you already have concerns.
1533 return vm.runInThisContext(data, lumbarFile);
16 } catch (err) {
170 var line;
180 try {
190 var esprima = require('esprima');
200 console.log(err.stack, esprima.parse(data));
21 } catch (err) {
220 if (err.lineNumber) {
230 line = ':' + err.lineNumber;
24 }
25 }
260 throw new Error('Failed to load config ' + lumbarFile + line + ': ' + err);
27 }
28};
29
30/**
31 *
32 * @name load
33 * @function This function loads the lumbar JSON file, and returns
34 * helper methods associated with accessing its specific data.
35 * @param {string} lumbarFile the location of the lumbar file.
36 * @return {Object}
37 */
381exports.load = function(lumbarFile) {
3927 fu.lookupPath('');
40
4127 var config = exports.readConfig(lumbarFile);
4227 fu.lookupPath(path.dirname(lumbarFile));
43
4427 return exports.create(config);
45};
46
471exports.create = function(config) {
48125 var packageList, moduleList;
49
50125 function loadPackageList() {
51125 if (!config.packages) {
52113 config.packages = { web: { name: '' } };
53 }
54
55125 packageList = _.keys(config.packages);
56 }
57125 function loadModuleList() {
58125 if (!config.modules) {
591 throw new Error('No modules object defined');
60 }
61124 moduleList = _.keys(config.modules);
62 }
63
64125 loadPackageList();
65125 loadModuleList();
66
67124 return {
68 /** @typedef {Object} The raw lumbar file as a JSON object. */
69 attributes: config,
70 loadPrefix: function() {
7146 return config.loadPrefix || '';
72 },
73
74 /**
75 *
76 * @name packageList
77 * @function This function returns the list of packages found
78 * in the lumbar file.
79 * @return {Array.<Object>} array of package(s).
80 */
81 packageList: function() {
8227 return packageList;
83 },
84
85 /**
86 *
87 * @name combineModules
88 * @function This functions checks to see if the package, pkg,
89 * is going to combine all its modules or not.
90 * @param {string} pkg the name of the package
91 * @return {boolean} is this package destined to be combined?
92 */
93 combineModules: function(pkg) {
941809 if (config && config.packages && config.packages[pkg]) {
951553 return config.packages[pkg].combine;
96 }
97256 return false;
98 },
99 platformList: function(pkg) {
10086 if (!pkg) {
10152 return config.platforms || [''];
102 } else {
10334 if (config.packages[pkg]) {
10434 return config.packages[pkg].platforms || this.platformList();
105 }
1060 return this.platformList();
107 }
108 },
109
110 moduleList: function(pkg) {
111234 return (config.packages[pkg] || {}).modules || _.keys(config.modules);
112 },
113
114 module: function(name) {
115480 var ret = config.modules[name];
116480 if (ret) {
117478 ret.name = name;
118 }
119480 return ret;
120 },
121 isAppModule: function(module) {
12273 var app = config.application;
12373 return (app && app.module) === (module.name || module);
124 },
125 scopedAppModuleName: function(module) {
12643 var app = config.application;
12743 if (this.isAppModule(module)) {
1284 return 'module.exports';
129 } else {
13039 var app = config.application;
13139 return app && app.name;
132 }
133 },
134
135 routeList: function(module) {
13622 return config.modules[module].routes;
137 },
138
139 serialize: function() {
1401 function objectClone(object) {
14113 var clone = object;
142
14313 if (object && object.serialize) {
144 // Allow specialized objects to handle themselves
1450 clone = object.serialize();
14613 } else if (_.isArray(object)) {
1471 clone = _.map(object, objectClone);
14812 } else if (_.isObject(object)) {
1497 clone = {};
1507 _.each(object, function(value, name) {
15110 clone[name] = objectClone(value);
152 });
153 }
154
155 // Collapse simple resources
15613 if (clone.src && _.keys(clone).length === 1) {
1570 clone = clone.src;
158 }
159
16013 return clone;
161 }
162
1631 return objectClone(this.attributes);
164 }
165 };
166};
167
168

/Users/kpdecker/dev/walmart/lumbar/lib/context.js

85%
109
93
16
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 fs = require('fs'),
4 fu = require('./fileUtil');
5
61function Context(options, config, plugins, libraries, event) {
72057 this._package = options.package;
82057 this._platform = options.platform;
92057 this._plugins = plugins;
102057 this.mode = options.mode;
112057 this.module = options.module;
122057 this.fileConfig = options.fileConfig;
132057 this.resource = options.resource;
142057 this.config = config;
152057 this.libraries = libraries || options.libraries;
162057 this.event = event || options.event;
17}
181Context.prototype = {
19 fileUtil: fu,
20
21 clone: function(options) {
221935 var ret = new Context(this, this.config);
231935 ret.parent = this;
241935 var prototype = Object.keys(Context.prototype);
251935 for (var name in this) {
2660098 if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) {
2734943 ret[name] = this[name];
28 }
29 }
301935 if (options) {
31255 _.extend(ret, options);
32255 ret._package = options.package || this._package;
33255 ret._platform = options.platform || this._platform;
34 }
351935 return ret;
36 },
37
38 fileNamesForModule: function(mode, moduleName, callback) {
3983 var context = this.clone();
4083 context.mode = mode;
4183 context.module = moduleName && this.config.module(moduleName);
4283 if (moduleName && !context.module) {
432 return callback(new Error('Unknown module "' + moduleName + '"'));
44 }
45
4681 this.plugins.outputConfigs(context, function(err, configs) {
4781 if (err) {
480 return callback(err);
49 }
50
5181 async.map(configs, function(config, callback) {
52107 var fileContext = context.clone();
53107 fileContext.fileConfig = config;
54107 fileContext._plugins.fileName(fileContext, function(err, fileName) {
55107 config.fileName = fileName;
56107 callback(err, config);
57 });
58 },
59 callback);
60 });
61 },
62
63 loadResource: function(resource, callback) {
64412 if (!callback) {
65 // if only single param, assume as callback and resource from context
660 resource = this.resource;
670 callback = resource;
68 }
69
70412 var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src};
71
72412 function loaded(err, data) {
73 /*jshint eqnull: true */
74412 if (err) {
752 var json = '';
762 try {
77 // Output JSON for the resource... but protect ourselves from a failure masking a failure
782 resource = _.clone(resource);
792 delete resource.library;
802 delete resource.enoent;
812 json = '\n\t' + JSON.stringify(resource);
82 } catch (err) { /* NOP */ }
83
842 var errorWrapper = new Error('Failed to load resource "' + fileInfo.name + '"' + json + '\n\t' + (err.stack || err));
852 errorWrapper.source = err;
862 errorWrapper.code = err.code;
872 callback(errorWrapper);
882 return;
89 }
90410 fileInfo.inputs = data.inputs;
91410 fileInfo.generated = data.generated;
92410 fileInfo.noSeparator = data.noSeparator;
93410 fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings;
94410 fileInfo.content = data.data != null ? data.data : data;
95
96 // Ensure that we dump off the stack
97410 _.defer(function() {
98410 callback(err, fileInfo);
99 });
100 }
101
102412 if (typeof resource === 'function') {
103193 resource(this, loaded);
104219 } else if (resource.src) {
105 // Assume a file page, attempt to load
106206 fu.readFile(resource.src, loaded);
107 } else {
10813 loaded(undefined, {data: '', noSeparator: true, inputs: resource.dir ? [resource.dir] : []});
109 }
110
111412 return fileInfo;
112 },
113
114 outputFile: function(writer, callback) {
115139 var context = this;
116139 context.plugins.file(context, function(err) {
117139 if (err) {
1180 return callback(err);
119 }
120
121139 context.plugins.fileName(context, function(err, fileName) {
122139 if (err) {
1230 return callback(err);
124 }
125
126139 context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension;
127139 context.fileName = context.outdir + '/' + context.buildPath;
128139 writer(function(err, data) {
129139 data = _.defaults({
130 fileConfig: context.fileConfig,
131 platform: context.platform,
132 package: context.package,
133 mode: context.mode
134 }, data);
135
136139 if (err) {
1371 fs.unlink(context.fileName);
138 } else {
139138 context.event.emit('output', data);
140 }
141139 context.fileCache = undefined;
142139 callback(err, data);
143 });
144 });
145 });
146 },
147
148 get description() {
1490 var ret = 'package:' + this.package + '_platform:' + this.platform;
1500 if (this.mode) {
1510 ret += '_mode:' + this.mode;
152 }
1530 if (this.fileName) {
1540 ret += '_config:' + this.fileName;
155 }
1560 if (this.module) {
1570 ret += '_module:' + (this.module.name || this.module);
158 }
1590 if (this.resource) {
160 // TODO : Anything better for this?
1610 ret += '_resource:' + (this.resource.src || this.resource);
162 }
1630 return ret;
164 },
165
1661228 get plugins() { return this._plugins; },
167
1684963 get package() { return this._package; },
1693496 get platform() { return this._platform; },
170 get platformPath() {
171151 return this.platform ? this.platform + '/' : '';
172 },
173
174 get combined() {
1751809 return this.config.combineModules(this.package);
176 },
177 get baseName() {
178220 if (!this.combined) {
179152 return this.module.name;
180 } else {
18168 return (this.config.attributes.packages[this.package] || {}).name || this.package;
182 }
183 },
184
185 get resources() {
186282 if (this.parent) {
1870 return this.parent.resources;
188 } else {
189282 return this._resources;
190 }
191 },
192 set resources(value) {
193340 if (this.parent) {
194304 delete this.parent;
195 }
196340 this._resources = value;
197 }
198};
199
2001module.exports = Context;
201

/Users/kpdecker/dev/walmart/lumbar/lib/fileUtil.js

92%
176
163
13
LineHitsSource
11var _ = require('underscore'),
2 EventEmitter = require('events').EventEmitter,
3 fs = require('fs'),
4 path = require('path'),
5 handlebars = require('handlebars');
6
71const EMFILE_RETRY = 250;
8
91var fileCache = {};
10
111exports = module.exports = new EventEmitter();
12
131function cacheRead(path, exec, callback) {
14348 path = exports.resolvePath(path);
15
16348 var cache = fileCache[path];
17348 if (cache) {
18192 if (cache.data) {
19132 callback(undefined, cache);
20 } else {
2160 cache.pending.push(callback);
22 }
23192 return;
24 }
25
26156 cache = fileCache[path] = {
27 pending: [callback],
28 artifacts: {}
29 };
30
31156 exec(path, function _callback(err, data) {
32156 if (err && err.code === 'EMFILE') {
330 setTimeout(exec.bind(this, path, _callback), EMFILE_RETRY);
34 } else {
35156 if (err) {
362 delete fileCache[path];
37 }
38
39156 cache.data = data;
40156 cache.pending.forEach(function(callback) {
41216 callback(err, cache);
42 });
43156 exports.emit('cache:set', path);
44 }
45 });
46}
47
481exports.resetCache = function(filePath) {
49233 filePath = filePath && path.normalize(filePath);
50233 exports.emit('cache:reset', filePath);
51
52233 if (filePath) {
53171 filePath = exports.resolvePath(filePath);
54171 delete fileCache[filePath];
55 } else {
5662 fileCache = {};
57 }
58};
59
601var lookupPath;
611exports.resolvePath = function(pathName) {
62 // Poormans path.resolve. We aren't able to use the bundled path.resolve due to
63 // it throwing sync EMFILE errors without a type to key on.
641255 if (lookupPath
65 && (pathName[0] !== '/' && pathName.indexOf(':/') === -1 && pathName.indexOf(':\\') === -1)
66 && pathName.indexOf(lookupPath) !== 0) {
67847 return lookupPath + pathName;
68 } else {
69408 return pathName;
70 }
71};
721exports.makeRelative = function(pathName) {
73561 if (pathName.indexOf(lookupPath) === 0) {
74543 return pathName.substring(lookupPath.length);
75 } else {
7618 return pathName;
77 }
78};
79
801exports.lookupPath = function(pathName) {
81135 if (pathName !== undefined) {
8283 lookupPath = pathName;
8383 if (lookupPath && !/\/$/.test(lookupPath)) {
8436 lookupPath += '/';
85 }
86 }
87135 return lookupPath;
88};
89
901exports.stat = function(file, callback) {
91796 fs.stat(file, function(err, stat) {
92796 if (err && err.code === 'EMFILE') {
930 setTimeout(exports.stat.bind(exports, file, callback), EMFILE_RETRY);
94 } else {
95796 callback(err, stat);
96 }
97 });
98};
99
1001exports.readFileSync = function(file) {
10133 return fs.readFileSync(exports.resolvePath(file));
102};
1031exports.readFile = function(file, callback) {
104230 cacheRead(file, fs.readFile.bind(fs), function(err, cache) {
105230 callback(err, cache && cache.data);
106 });
107};
1081exports.readFileArtifact = function(file, name, callback) {
10958 cacheRead(file, fs.readFile.bind(fs), function(err, cache) {
11058 var artifacts = cache.artifacts;
11158 callback(err, {data: cache.data, artifact: artifacts[name]});
112 });
113};
1141exports.setFileArtifact = function(path, name, artifact) {
11524 path = exports.resolvePath(path);
116
11724 var cache = fileCache[path];
11824 if (cache) {
11924 cache.artifacts[name] = artifact;
120 }
121};
122
1231exports.readdir = function(dir, callback) {
12460 cacheRead(dir, fs.readdir.bind(fs), function(err, cache) {
12560 callback(err, cache && cache.data);
126 });
127};
128
1291exports.ensureDirs = function(pathname, callback) {
130239 var dirname = path.dirname(pathname);
131239 exports.stat(dirname, function(err) {
132239 if (err && err.code === 'ENOENT') {
133 // If we don't exist, check to see if our parent exists before trying to create ourselves
13448 exports.ensureDirs(dirname, function() {
13548 fs.mkdir(dirname, parseInt('0755', 8), function _callback(err) {
13648 if (err && err.code === 'EMFILE') {
1370 setTimeout(fs.mkdir.bind(fs, dirname, parseInt('0755', 8), _callback), EMFILE_RETRY);
138 } else {
139 // Off to the races... and we lost.
14048 callback(err && err.code === 'EEXIST' ? undefined : err);
141 }
142 });
143 });
144 } else {
145191 callback();
146 }
147 });
148};
149
1501exports.writeFile = function(file, data, callback) {
151133 exports.resetCache(file);
152
153133 exports.ensureDirs(file, function(err) {
154133 if (err) {
1550 return callback(err);
156 }
157
158133 fs.writeFile(file, data, 'utf8', function _callback(err) {
159133 if (err && err.code === 'EMFILE') {
1600 setTimeout(fs.writeFile.bind(fs, file, data, 'utf8', _callback), EMFILE_RETRY);
161 } else {
162133 callback(err);
163 }
164 });
165 });
166};
167
168/**
169 * Takes a given input and returns the files that are represented.
170 *
171 * pathname may be:
172 * a resource object
173 * a path on the file system
174 * an array of resources
175 */
1761exports.fileList = function(pathname, extension, callback, dirList, resource, srcDir) {
177848 if (_.isFunction(extension)) {
1785 callback = extension;
1795 extension = /.*/;
180 }
181
182848 if (_.isArray(pathname)) {
183293 var files = pathname;
184293 pathname = '';
185293 if (!files.length) {
186119 return callback(undefined, []);
187 }
188174 return handleFiles(false, undefined, _.uniq(files));
189555 } else if (!dirList) {
190395 if (pathname.src) {
1910 resource = resource || pathname;
1920 pathname = pathname.src;
193 }
194
195395 pathname = exports.resolvePath(pathname);
196 }
197555 if (resource && resource.src) {
198183 resource = _.clone(resource);
199183 delete resource.src;
200 }
201
202555 function handleFiles(dirname, err, files, srcDir) {
203231 if (err) {
2040 return callback(err);
205 }
206
207231 var ret = [],
208 count = 0,
209 expected = files.length,
210 prefix = pathname ? pathname.replace(/\/$/, '') + '/' : '';
211
212231 function complete(files, index) {
213592 count++;
214
215592 ret[index] = files;
216
217592 if (count === expected) {
218230 ret = _.flatten(ret);
219
220230 if (srcDir) {
22156 ret = ret.map(function(file) {
222123 if (!file.src && !file.dir) {
2230 file = { src: file };
224 }
225123 file.srcDir = srcDir;
226123 return file;
227 });
228 }
229
230230 if (dirname) {
23156 ret.push(_.defaults({dir: dirname}, resource));
23256 ret = ret.sort(function(a, b) {
233240 return (a.dir || a.src || a).localeCompare(b.dir || b.src || b);
234 });
235 }
236
237230 callback(undefined, ret);
238 }
239 }
240
241231 if (!files.length) {
2421 callback(undefined, []);
243 }
244
245231 files.forEach(function(file, index) {
246592 var fileResource = resource;
247592 if (file.src) {
248183 fileResource = resource || file;
249183 file = file.src;
250409 } else if (_.isObject(file)) {
25164 complete(file, index);
25264 return;
253 }
254
255528 exports.fileList(prefix + file, extension, function(err, files) {
256528 if (err) {
2570 callback(err);
2580 return;
259 }
260
261528 complete(files, index);
262 }, dirname, fileResource, srcDir);
263 });
264 }
265
266555 exports.stat(pathname, function(err, stat) {
267555 if (err) {
26847 if (err.code === 'ENOENT') {
26947 callback(undefined, [ _.extend({src: exports.makeRelative(pathname), enoent: true}, resource) ]);
270 } else {
2710 callback(err);
272 }
27347 return;
274 }
275
276508 if (stat.isDirectory()) {
27757 exports.readdir(pathname, function(err, files) {
27857 var _pathname = exports.makeRelative(pathname);
27957 handleFiles(_pathname, undefined, files, srcDir || _pathname);
280 });
281 } else {
282451 pathname = exports.makeRelative(pathname);
283
284451 var basename = path.basename(pathname),
285 namePasses = basename[0] !== '.' && basename !== 'vendor' && (!dirList || extension.test(pathname)),
286 ret = [];
287451 if (namePasses) {
288393 if (resource) {
289170 ret = [ _.defaults({src: pathname, srcDir: srcDir}, resource) ];
290223 } else if (srcDir) {
29170 ret = [ { src: pathname, srcDir: srcDir } ];
292 } else {
293153 ret = [ pathname ];
294 }
295 }
296451 callback(undefined, ret);
297 }
298 });
299};
300
301//accepts a template string or a filename ending in .handlebars
3021exports.loadTemplate = function(template, splitOnDelimiter, callback) {
30340 function compile(templateStr, callback) {
30429 try {
30529 if (splitOnDelimiter) {
30619 callback(null, templateStr.split(splitOnDelimiter).map(function(bit) {
30738 return handlebars.compile(bit);
308 }));
309 } else {
31010 callback(null, handlebars.compile(templateStr));
311 }
312 } catch (e) {
3130 callback(e);
314 }
315 }
31640 if (template.match(/\.handlebars$/)) {
31719 exports.readFileArtifact(template, 'template', function(err, data) {
31819 if (err) {
3191 return callback(err);
320 }
321
32218 if (data.artifact) {
32310 callback(undefined, data.artifact);
324 } else {
3258 compile(data.data.toString(), function(err, data) {
3268 if (!err) {
3278 exports.setFileArtifact(template, 'template', data);
328 }
3298 callback(err, data);
330 });
331 }
332 });
333 } else {
33421 compile(template, callback);
335 }
336};
337

/Users/kpdecker/dev/walmart/lumbar/lib/jsCombine.js

61%
62
38
24
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 FileMap = require('./util/file-map'),
4 fu = require('./fileUtil'),
5 ChildPool = require('./child-pool');
6
71var uglify = new ChildPool(__dirname + '/uglify-worker');
8
91exports.combine = function(context, files, output, minimize, noSeparator, callback) {
10
11107 function outputIfCompleted() {
12369 if (completed >= files.length) {
13106 var lastEl,
14 map = new FileMap(output),
15 warnings = [],
16
17 tasks = [];
18
19106 _.each(content, function(el) {
20369 var content = el.content.toString();
21
22369 if (!noSeparator && (!lastEl || !lastEl.noSeparator) && map.content()) {
23114 map.add(undefined, '\n;;\n');
24 }
25
26369 map.add(el.generated ? undefined : el.name, content, el);
27
28369 lastEl = el;
29 }, '');
30
31106 var inputs = [];
32106 content.forEach(function(el) {
33369 if (el.inputs) {
3493 inputs.push.apply(inputs, el.inputs);
35276 } else if (el.name) {
36178 inputs.push(el.name);
37 }
38 });
39106 inputs = _.unique(inputs);
40
41 // "Serialize" the data in the map
42106 tasks.push(function(callback) {
43106 callback(undefined, map.content());
44 });
45
46 // Minimize the content if flagged
47106 if (minimize) {
480 var uglifyConfig = context.config.attributes.uglify || {};
49
500 tasks.push(function(data, callback) {
510 uglify.send({
52 output: output,
53 data: data,
54 compressorOptions: uglifyConfig.compressor,
55 manglerOptions: uglifyConfig.mangler,
56 outputOptions: uglifyConfig.output,
57 sourceMap: context.options.sourceMap ? map.sourceMap() : undefined
58 },
59 function(err, data) {
600 if (err) {
610 return callback(err);
62 }
63
640 _.each(data.warnings, function(msg) {
650 var match = /(.*?)\s*\[.*:(\d+),(\d+)/.exec(msg);
660 if (match) {
670 var msg = match[1],
68 line = parseInt(match[2], 10),
69 column = match[3],
70 context = map.context(line, column);
71
720 if (context && (!context.fileContext || !context.fileContext.ignoreWarnings)) {
730 context.msg = msg;
740 warnings.push(context);
75 }
76 } else {
770 warnings.push({msg: msg});
78 }
79 });
80
810 if (data.sourceMap) {
82 // Remap the sourcemap output for the point that it is actually used for output
83 // We need to restore the source map here as uglify will remove the original
84 // Declaration
850 map.sourceMap = function() { return data.sourceMap; };
86 }
87
880 callback(err, data.data);
89 });
90 });
91 }
92
93 // Output the source map if requested
94106 var sourceMap = context.options.sourceMap;
95106 if (sourceMap) {
960 var inlineSourceMap = sourceMap === true;
97
980 tasks.push(function(data, callback) {
990 map.writeSourceMap({
100 mapDestination: !inlineSourceMap && (sourceMap + '/' + context.buildPath),
101 outputSource: inlineSourceMap,
102 callback: function(err) {
1030 if (inlineSourceMap) {
1040 data += '\n' + map.sourceMapToken();
105 }
1060 callback(err, data);
107 }
108 });
109 });
110 }
111
112 // Output step
113106 tasks.push(function(data, callback) {
114106 fu.writeFile(output, data, callback);
115 });
116
117 // Excute everything and return to the caller
118106 async.waterfall(tasks, function(err) {
119106 if (err) {
1200 callback(new Error('Combined output "' + output + '" failed\n\t' + err));
1210 return;
122 }
123
124106 callback(undefined, {
125 fileName: output,
126 inputs: inputs,
127 warnings: warnings
128 });
129 });
130 }
131 }
132107 var completed = 0,
133 content = [];
134
135107 files.forEach(function(resource) {
136370 var fileInfo = context.loadResource(resource, function(err) {
137370 if (err) {
1381 callback(err);
1391 return;
140 }
141
142369 completed++;
143369 outputIfCompleted();
144 });
145370 content.push(fileInfo);
146 });
147};
148

/Users/kpdecker/dev/walmart/lumbar/lib/libraries.js

93%
165
154
11
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 config = require('./config'),
4 fs = require('fs'),
5 fu = require('./fileUtil'),
6 path = require('path');
7
81function Libraries(options) {
9118 this.options = options;
10118 this.mixins = [];
11118 this.configs = [];
12}
13
141Libraries.prototype.initialize = function(context, callback) {
15122 this.mixins = [];
16122 this.originalConfig = _.clone(context.config.attributes);
17
18122 var commandLineLibraries = this.options.libraries || [],
19 configLibraries = context.config.attributes.libraries || context.config.attributes.mixins || [],
20
21 allLibraries = commandLineLibraries.concat(configLibraries);
22
23122 delete context.config.attributes.mixins;
24122 async.forEachSeries(allLibraries, _.bind(this.load, this, context), callback);
25};
261Libraries.prototype.load = function(context, libraryConfig, callback) {
27 // Allow mixins to be passed directly
2867 var root = libraryConfig.root,
29 configPath,
30 self = this;
31
32 // Or as a file reference
3367 if (!_.isObject(libraryConfig)) {
346 root = root || libraryConfig;
35
36 // If we have a dir then pull lumbar.json from that
376 try {
386 var stat = fs.statSync(fu.resolvePath(libraryConfig));
396 if (stat.isDirectory()) {
403 libraryConfig = libraryConfig + '/lumbar.json';
413 } else if (root === libraryConfig) {
42 // If we are a file the root should be the file's directory unless explicitly passed
433 root = path.dirname(root);
44 }
45 } catch (err) {
460 return callback(err);
47 }
48
496 configPath = fu.resolvePath(libraryConfig);
506 libraryConfig = config.readConfig(configPath);
51 }
52
53 // To make things easy force root to be a dir
5467 if (root && !/\/$/.test(root)) {
5524 root = root + '/';
56 }
57
5867 if (!libraryConfig.name) {
594 return callback(new Error('Mixin with root "' + root + '" is missing a name.'));
60 }
61
6263 var mixins = libraryConfig.mixins,
63 toRegister = {};
6463 delete libraryConfig.mixins;
65
6663 function mapMixin(mixin, name) {
67 // Only register once, giving priority to an explicitly defined mixin
6843 if (!toRegister[name]) {
6942 toRegister[name] = {
70 serialize: function() {
710 return {name: this.name, library: this.parent.name};
72 },
73 attributes: mixin,
74 parent: libraryConfig,
75 root: root
76 };
77 }
78 }
79
80 // Read each of the mixins that are defined in the config
8163 _.each(mixins, mapMixin, this);
82
83 // Make mixin modules accessible as normal mixins as well
8463 _.each(libraryConfig.modules, mapMixin, this);
85
86 // After we've pulled everything in register
8763 _.each(toRegister, function(mixin, name) {
8842 this.mixins[name] = this.mixins[name] || [];
8942 var list = this.mixins[name];
9042 list.push(mixin);
91 }, this);
92
93 // Run all of the plugins that are concerned with this.
9463 libraryConfig.root = root;
9563 libraryConfig.path = configPath;
9663 context.loadedLibrary = libraryConfig;
9763 context.plugins.loadMixin(context, function(err) {
9863 delete libraryConfig.root;
99
100 // And then splat everything else into our config
10163 _.defaults(context.config.attributes, _.omit(context.loadedLibrary, 'name', 'path'));
102
10363 libraryConfig.serialize = function() {
1040 return { library: this.name };
105 };
106
10763 libraryConfig.root = root;
10863 self.configs.push(libraryConfig);
109
11063 callback(err);
111 });
112};
113
1141Libraries.prototype.findDecl = function(mixins, mixinName) {
1159 if (!mixinName) {
1160 mixinName = {name: mixinName};
117 }
118
1199 return _.find(mixins, function(mixinDecl) {
12011 return (mixinDecl.name || mixinDecl) === mixinName.name
121 && (!mixinDecl.library || mixinDecl.library === mixinName.library);
122 });
123};
124
1251Libraries.prototype.moduleMixins = function(module) {
126 // Perform any nested mixin lookup
127282 var mixins = _.clone(module.mixins || []);
128282 for (var i = 0, len = mixins.length; i < len; i++) {
12962 var firstInclude = mixins[i],
130 mixin = this.getMixin(firstInclude),
131 added = [i, 0];
132
13360 if (!mixin) {
1340 throw new Error('Unable to find mixin "' + firstInclude + '"');
135 }
136
137 // Check if we need to include any modules that this defined
13860 _.each(mixin.attributes.mixins, function(mixinInclude) {
1394 if (!this.findDecl(mixins, mixinInclude)) {
1404 added.push(mixinInclude);
141 }
142 }, this);
143
144 // If we've found any new mixins insert them at the current spot and iterate
145 // over those items
14660 if (added.length > 2) {
1474 mixins.splice.apply(mixins, added);
1484 i--;
149 }
150 }
151
152 // Extend the module with each of the mixins content, giving priority to the module
153280 return _.map(mixins.reverse(), function(mixin) {
15460 var mixinConfig = mixin.name && mixin,
155 name = mixin;
15660 if (mixinConfig) {
15720 mixinConfig = _.clone(mixinConfig);
15820 delete mixinConfig.library;
15920 delete mixinConfig.container;
160 }
16160 mixin = _.extend(
162 {},
163 this.getMixin(name),
164 mixinConfig);
16560 if (!mixin.attributes) {
1660 throw new Error('Mixin "' + name.name || name + '" is not defined.');
167 }
168
169 // Save a distinct instance of the config for resource extension
17060 if (mixinConfig) {
17120 mixinConfig = _.clone(mixinConfig);
17220 delete mixinConfig.overrides;
17320 delete mixinConfig.name;
174 }
175
17660 return {
177 library: mixin,
178 mixinConfig: mixinConfig
179 };
180 }, this);
181};
182
1831Libraries.prototype.mapFiles = function(value, library, config) {
184157 var files = _.map(value, function(resource) {
185240 return this.mapFile(resource, library, config);
186 }, this);
187395 files = _.filter(files, function(file) { return file; });
188
189156 return files;
190};
1911Libraries.prototype.mapFile = function(resource, library, config) {
192 // If explicitly declared the resource library takes precedence
193240 if (_.isString(resource.library || resource.mixin)) {
1943 library = this.getConfig(resource.library || resource.mixin);
1953 if (!library) {
1961 throw new Error('Mixin "' + (resource.library || resource.mixin) + '" not found');
197 }
1982 delete resource.mixin;
199 }
200
201 // If no mixin was defined on either side then return the identity
202239 if (!library) {
203172 return resource;
204 }
205
20667 if (_.isString(resource)) {
20756 resource = {src: resource};
208 } else {
20911 resource = _.clone(resource);
210 }
211
21267 var src = resource.src || resource.dir;
213
214 // Include any config information such as env or platform that may have been
215 // specified on the library settings
21667 _.extend(resource, config);
217
21867 if (src) {
21965 var librarySrc = (library.root || '') + src;
220
22165 var override = library.overrides && library.overrides[src];
22265 if (override) {
2236 resource.originalSrc = librarySrc;
2246 librarySrc = _.isString(override) ? override : src;
22559 } else if (override === false) {
2261 return;
227 }
228
22964 if (resource.src) {
23063 resource.src = librarySrc;
2311 } else if (resource.dir) {
2321 resource.dir = librarySrc;
233 }
234 }
235
23666 resource.library = library;
23766 return resource;
238};
239
2401Libraries.prototype.getMixin = function(name) {
241122 var mixins = (this.mixins && this.mixins[name.name || name]) || [],
242 library = name.library || name.container;
243122 if (mixins.length > 1 && !library) {
2441 throw new Error(
245 'Duplicate mixins found for "' + (name.name || name) + '"'
246 + _.map(mixins, function(mixin) {
2472 return ' parent: "' + mixin.parent.name + '"';
248 }).join(''));
249 }
250
251121 if (library) {
2529 if (name.name === undefined) {
2530 var found = _.find(this.configs, function(config) {
2540 return config.name === library;
255 });
2560 if (!found) {
2570 throw new Error('Unable to find library "' + library + '"');
258 }
2590 return found;
260 }
261
2629 var found = _.find(mixins, function(mixin) {
26317 return mixin.parent.name === library;
264 });
2659 if (found) {
2668 return found;
267 } else {
2681 throw new Error('Mixin named "' + name.name + '" not found in library "' + library + '"');
269 }
270112 } else if (mixins.length === 1) {
271112 return mixins[0];
272 }
273};
2741Libraries.prototype.getConfig = function(name) {
2759 return _.find(this.configs, function(config) { return config.name === name; });
276};
277
2781Libraries.prototype.resolvePath = function(name, mixin) {
27943 if (!mixin) {
28036 return name;
281 }
282
2837 var override = mixin.overrides && mixin.overrides[name];
2847 if (override) {
2852 return _.isString(override) ? override : name;
286 }
287
2885 return mixin.root + name;
289};
290
2911Libraries.prototype.mergeHash = function(hashName, input, mixin, output) {
29260 if (mixin[hashName]) {
293 // Close the value to make sure that we are not overriding anything
29410 if (!output[hashName] || output[hashName] === input[hashName]) {
2958 output[hashName] = _.clone(input[hashName] || {});
296 }
29710 _.each(mixin[hashName], function(value, key) {
29816 if (!input[hashName] || !(key in input[hashName])) {
29912 output[hashName][key] = value;
300 }
301 });
30210 return true;
303 }
304};
3051Libraries.prototype.mergeFiles = function(fieldName, input, mixinData, output, library) {
30632 if (mixinData[fieldName]) {
3078 mixinData = _.isArray(mixinData[fieldName]) ? mixinData[fieldName] : [mixinData[fieldName]];
308
3098 var configData = input[fieldName] || [];
3108 if (!output[fieldName] || configData === output[fieldName]) {
3116 output[fieldName] = _.clone(configData);
312 }
3138 if (!_.isArray(configData)) {
3142 configData = [configData];
315 }
3168 if (!_.isArray(output[fieldName])) {
3171 output[fieldName] = [output[fieldName]];
318 }
319
320 // Insert point is at the start of the upstream list, which we are
321 // assuming occurs at length postions from the end.
3228 _.each(mixinData, function(value) {
323 //Make the include relative to the mixin
32411 value = (library.root || '') + value;
325
32611 output[fieldName].splice(
327 output[fieldName].length - configData.length,
328 0,
329 {src: value, library: library});
330 });
331
3328 return true;
333 }
334};
335
3361module.exports = Libraries;
337

/Users/kpdecker/dev/walmart/lumbar/lib/lumbar.js

85%
160
137
23
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 build = require('./build'),
4 combine = require('./jsCombine'),
5 configLoader = require('./config'),
6 Context = require('./context'),
7 EventEmitter = require('events').EventEmitter,
8 fs = require('fs'),
9 fu = require('./fileUtil'),
10 Libraries = require('./libraries'),
11 plugin = require('./plugin'),
12 WatchManager = require('./watch-manager');
13
141exports.build = build;
151exports.fileUtil = fu;
161exports.plugin = plugin.plugin;
171exports.combine = combine.combine;
181exports.config = configLoader;
19
20/**
21 *
22 * @name init
23 * @function This function initializes a Lumbar instance
24 * @param {string} lumbarFile The lumbarFile is the main
25 * file. Its responsible to define all the platforms,
26 * packages, modules, and templates for Lumbar to use.
27 * @param {Object} options supports the following options:
28 * packageConfigFile (string): name of the package config file.
29 * outdir (string): path to directory of where to output the files.
30 * minimize (boolean): Should we minimize the files?
31 * @return {Object.<Function>}
32 */
331exports.init = function(lumbarFile, options) {
3423 var outdir = options.outdir,
35 plugins = plugin.create(options),
36 libraries = new Libraries(options),
37 config,
38 configCache,
39 context;
40
4123 function logError(err) {
4252 if (err) {
431 event.emit('error', err);
44 }
45 }
46
4723 function loadConfig(path, callback) {
4827 try {
4927 fu.resetCache();
50
5127 configCache = {};
5227 config = configLoader.load(path);
5327 plugins.initialize(config);
54
5527 config.outdir = options.outdir = outdir || config.attributes.output;
56
5727 context = new Context(options, config, plugins, libraries, event);
5827 context.options = options;
5927 context.configCache = configCache;
60
6127 libraries.initialize(context, function(err) {
6227 if (err) {
630 return callback(err);
64 }
65
6627 plugins.loadConfig(context, function(err) {
6727 if (err) {
680 return callback(err);
69 }
70
7127 if (options.verbose) {
720 event.emit('log', 'Finalized config ' + JSON.stringify(context.config.serialize(), undefined, 2));
73 }
74
75 // Ensure that we have the proper build output
7627 if (!config.outdir) {
770 return callback(new Error('Output must be defined on the command line or config file.'));
78 }
7927 context.outdir = config.outdir;
80
8127 fu.ensureDirs(config.outdir + '/.', function() {
8227 var stat = fs.statSync(config.outdir);
8327 if (!stat.isDirectory()) {
840 callback(new Error('Output must be a directory'));
85 } else {
8627 callback();
87 }
88 });
89 });
90 });
91 } catch (err) {
920 callback(err);
93 }
94 }
95
9623 function buildPackages(packageName, modules, callback) {
9731 if (!callback) {
9811 callback = modules;
9911 modules = undefined;
100 }
101
102 // Allow a string or a list as modules input
10331 if (!_.isArray(modules)) {
10431 modules = [modules];
1050 } else if (!modules.length) {
106 // Special case empty array input to build all
1070 modules = [undefined];
108 }
109
11031 var options = {};
11131 if (typeof packageName === 'object') {
1120 options = packageName;
1130 packageName = options.package;
114 }
115
11631 var packageNames = packageName ? [packageName] : config.packageList(),
117 contexts = [];
118
11931 packageNames.forEach(function(pkg) {
12034 modules.forEach(function(module) {
12134 options.package = pkg;
12234 options.module = module || undefined; // '' -> undefined
123
12434 var platforms = config.platformList(pkg);
12534 platforms.forEach(function(platform) {
12645 options.platform = platform;
127
12845 var newContext = context.clone(options);
12945 contexts.push(newContext);
130 });
131 });
132 });
133
13431 async.forEach(contexts, buildPlatform, callback);
135 }
13623 function buildPlatform(context, callback) {
13777 var modes = context.mode ? [context.mode] : plugins.modes();
138
13977 async.forEach(modes, function(mode, callback) {
140168 buildMode(mode, context, callback);
141 },
142 callback);
143 }
14423 function buildMode(mode, context, callback) {
145168 var modules = context.module ? [context.module] : config.moduleList(context.package);
146
147168 context = context.clone();
148168 context.mode = mode;
149168 context.modeCache = {};
150
151168 if (context.fileConfig) {
15220 processConfig(context.fileConfig, callback);
153 } else {
154148 plugins.outputConfigs(context, function(err, configs) {
155148 if (err) {
1560 return callback(err);
157 }
158148 async.forEach(configs, processConfig, callback);
159 });
160 }
161
162168 function processConfig(fileConfig, callback) {
163178 var fileContext = context.clone(true);
164178 fileContext.fileConfig = fileConfig;
165178 fileContext.resources = [];
166178 fileContext.combineResources = {};
167178 fileContext.fileCache = fileContext.combined ? {} : undefined;
168
169178 async.forEach(modules, function(module, callback) {
170262 var moduleContext = fileContext.clone();
171262 moduleContext.module = module;
172
173262 buildModule(moduleContext, callback);
174 },
175 function(err) {
176178 if (err) {
1771 return callback(err);
178 }
179
180177 plugins.modeComplete(fileContext, callback);
181 });
182 }
183 }
18423 function buildModule(context, callback) {
185262 var module = config.module(context.module);
186262 if (!module) {
1870 return callback(new Error('Unable to find module "' + context.module + '"'));
188 }
189
190262 context.module = module;
191262 context.fileCache = context.combined ? context.fileCache : {};
192262 context.moduleCache = {};
193
194262 var resource = context.resource;
195262 if (resource) {
1966 resource = resource.originalResource || resource;
1976 processResources([resource]);
198 } else {
199 // Load all resources associated with this module
200256 build.loadResources(context, function(err, resources) {
201256 if (err) {
2020 return callback(err);
203 }
204256 processResources(resources);
205 });
206 }
207
208262 function processResources(resources) {
209262 build.processResources(resources, context, function(err, resources) {
210262 if (err) {
2110 return callback(err);
212 }
213
214262 context.moduleResources = resources;
215262 plugins.module(context, callback);
216 });
217 }
218 }
219
22023 var event = new EventEmitter(),
221 watch;
222
22323 function watchOutputHandler(status) {
22496 if (!watch) {
225 // We've been cleaned up but residuals may still exist, do nothing on this exec
22612 return;
227 }
228
22984 if (status.fileConfig.isPrimary) {
23031 delete status.fileConfig;
23153 } else if (status.fileConfig.isPrimary === false) {
232 // This config is directly linked to another meaning we don't want to watch on it as
233 // it will be rebuilt.
23412 return;
235 }
236
23772 var originalConfig = config;
23872 watch.moduleOutput(status, function() {
23932 if (config !== originalConfig) {
240 // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch)
2410 return;
242 }
243
24432 buildPlatform(context.clone(status), logError);
245 });
246 }
247
24823 return _.extend(event, {
249 config: function() {
2500 return config;
251 },
252
253 use: function(plugin) {
2540 plugins.use(plugin);
255 },
256 /**
257 *
258 * @name build
259 * @function This function builds out the package(s).
260 * @param {string} packageName the name of the package listed under
261 * 'packages' from the lumbarFile passed in during the call to init().
262 * @param {Function} callback the node process Function
263 */
264 build: function(packageName, modules, callback) {
26511 loadConfig(lumbarFile, function(err) {
26611 if (err) {
2670 if (!callback) {
2680 throw err;
269 }
2700 return callback(err);
271 }
272
27311 buildPackages(packageName, modules, callback);
274 });
275 },
276 watch: function(packageName, modules, callback) {
27716 if (!fs.watch) {
2780 throw new Error('Watch requires fs.watch, introduced in Node v0.6.0');
279 }
280
28116 watch = new WatchManager();
28216 watch.on('watch-change', function(info) {
28340 event.emit('watch-change', info);
284 });
285
28616 var self = this;
28716 loadConfig(lumbarFile, function(err) {
28816 if (err) {
2890 logError(err);
290 }
291
29216 if (!callback) {
29316 callback = modules;
29416 modules = undefined;
295 }
296
297 // Watch for changes in the config file
29820 var mixinPaths = _.filter(_.pluck(libraries.configs, 'path'), function(path) { return path; });
29916 watch.configFile(lumbarFile, mixinPaths, function() {
3004 config = undefined;
3014 self.watch(packageName, callback);
302 });
303
304 // If we have errored do not exec everything as it could be in an indeterminate state
30516 if (err) {
3060 return;
307 }
308
309 // Watch the individual components
31016 event.removeListener('output', watchOutputHandler);
31116 event.on('output', watchOutputHandler);
312
313 // Actual build everything
31416 var packages = packageName ? [packageName] : config.packageList();
31516 packages.forEach(function(name) {
31620 buildPackages(name, modules, logError);
317 });
318 });
319 },
320 unwatch: function() {
32112 event.removeListener('output', watchOutputHandler);
32212 if (watch) {
32312 watch.removeAllListeners();
32412 watch.reset();
32512 watch = undefined;
326 }
327 }
328 });
329};
330

/Users/kpdecker/dev/walmart/lumbar/lib/path-relative.js

89%
29
26
3
LineHitsSource
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
221var path = require('path');
23
24// path.relative(from, to)
25// posix version
261exports.relative = function(from, to) {
2728 from = path.resolve(from).substr(1);
2828 to = path.resolve(to).substr(1);
29
3028 function trim(arr) {
3156 var start = 0;
3256 for (; start < arr.length; start++) {
3356 if (arr[start] !== '') {
3456 break;
35 }
36 }
37
3856 var end = arr.length - 1;
3956 for (; end >= 0; end--) {
4056 if (arr[end] !== '') {
4156 break;
42 }
43 }
44
4556 if (start > end) {
460 return [];
47 }
4856 return arr.slice(start, end - start + 1);
49 }
50
5128 var fromParts = trim(from.split('/'));
5228 var toParts = trim(to.split('/'));
53
5428 var length = Math.min(fromParts.length, toParts.length);
5528 var samePartsLength = length;
5628 for (var i = 0; i < length; i++) {
57156 if (fromParts[i] !== toParts[i]) {
580 samePartsLength = i;
590 break;
60 }
61 }
62
6328 var outputParts = [];
6428 for (var i = samePartsLength; i < fromParts.length; i++) {
652 outputParts.push('..');
66 }
67
6828 outputParts = outputParts.concat(toParts.slice(samePartsLength));
69
7028 return outputParts.join('/');
71};
72

/Users/kpdecker/dev/walmart/lumbar/lib/plugin.js

99%
108
107
1
LineHitsSource
11var _ = require('underscore'),
2 path = require('path');
31const corePlugins = [
4 'mixin',
5 'styles-output', 'scripts-output', 'static-output',
6 'scope', 'router', 'template', 'inline-styles',
7 'coffee-script', 'stylus', 'handlebars',
8 'module-map', 'package-config', 'stylus-config',
9 'update-externals',
10 'inline-styles-resources', 'styles', 'scripts', 'static'
11];
121var fileUtils = require("/Users/kpdecker/dev/walmart/lumbar/lib/./fileUtil");
13
141var globalPlugins = {};
15
161exports.plugin = function(name, plugin) {
1720 globalPlugins[name] = plugin;
1820 plugin.id = name;
19};
20
211exports.plugin('module-map', require('./plugins/module-map'));
221exports.plugin('package-config', require('./plugins/package-config'));
231exports.plugin('router', require('./plugins/router'));
241exports.plugin('scope', require('./plugins/scope'));
251exports.plugin('stylus', require('./plugins/stylus'));
261exports.plugin('stylus-config', require('./plugins/stylus-config'));
271exports.plugin('coffee-script', require('./plugins/coffee-script'));
281exports.plugin('handlebars', require('./plugins/handlebars'));
291exports.plugin('inline-styles', require('./plugins/inline-styles'));
301exports.plugin('inline-styles-resources', require('./plugins/inline-styles-resources'));
311exports.plugin('mixin', require('./plugins/mixin'));
321exports.plugin('update-externals', require('./plugins/update-externals'));
331exports.plugin('template', require('./plugins/template'));
341exports.plugin('styles', require('./plugins/styles.js'));
351exports.plugin('server-scripts', require('./plugins/server-scripts.js'));
361exports.plugin('scripts', require('./plugins/scripts.js'));
371exports.plugin('static', require('./plugins/static.js'));
381exports.plugin('styles-output', require('./plugins/styles-output.js'));
391exports.plugin('scripts-output', require('./plugins/scripts-output.js'));
401exports.plugin('static-output', require('./plugins/static-output.js'));
41
421exports.create = function(options) {
43118 var plugins;
44118 var modes; // all registered modes
45118 var pluginModes; // map of modes and plugins scoped to the mode
46118 var modeAll; // plugins that are scoped to all modes
47
48118 function runPlugins(context, methodName, complete, failOver, noMode) {
492894 var len = 0,
50 pluginMode = pluginModes[context.mode] || [];
51
522894 return (function next(complete) {
53 /*jshint boss:true */
548135 var plugin;
558135 while (plugin = plugins[len++]) {
56 // if plugin shouldn't work with current mode, go to next
5753520 if (!noMode
58 && (!context.mode || pluginMode.indexOf(plugin) < 0)
59 && modeAll.indexOf(plugin) < 0) {
6023689 continue;
61 }
62
6329831 var method = plugin[methodName];
6429831 if (method) {
655902 if (complete) {
665771 process.nextTick(function() {
675771 method.call(plugin, context, next, complete);
68 });
695771 return;
70 } else {
71131 return method.call(plugin, context, next, complete);
72 }
73 }
74 }
75
76 // We're done, send data back
772233 if (complete) {
78 // async
79 // Clear out our stack under async mode to try to keep the stack somewhat sane.
802053 process.nextTick(function() {
812053 complete(undefined, failOver && failOver());
82 });
83 } else {
84 // sync
85180 return failOver && failOver();
86 }
87 })(complete);
88 }
89
90118 function registerPlugin(plugin) {
912272 var _plugin = globalPlugins[plugin] || plugin;
92
932272 var mode = _plugin.mode;
942272 if (mode) {
952153 if (_.isString(mode)) {
961670 mode = [mode];
97 }
982153 _.each(mode, function(_mode) {
992636 if (mode === 'all') {
100 // allow plugins to contribute new modes and participate in all modes
1010 modeAll.push(_plugin);
102 } else {
1032636 if (modes.indexOf(_mode) < 0) {
104364 modes.push(_mode);
105364 pluginModes[_mode] = [];
106 }
1072636 pluginModes[_mode].push(_plugin);
108 }
109 });
110 } else {
111119 modeAll.push(_plugin);
112 }
1132272 plugins.push(_plugin);
1142272 plugins.sort(function(a, b) {
11530078 return (a.priority || 50) - (b.priority || 50);
116 });
117 }
118
119118 return {
120 get: function(name) {
121 // Find the plugin with this id, if one exists
12267 var plugin = plugins.reduce(function(plugin, left) {
1231206 return plugin.id === name ? plugin : left;
124 });
125
126 // If the plugin was not found do not return the last item in the reduce
12767 if (plugin.id === name) {
12865 return plugin;
129 }
130 },
131 use: function(plugin) {
13211 if (plugin.path || (_.isString(plugin) && !globalPlugins[plugin])) {
1331 var pluginPath = plugin.path || plugin;
1341 var options = plugin.options;
1351 try {
1361 plugin = require(pluginPath);
137 } catch (e) {
1381 plugin = require(path.resolve(process.cwd(), fileUtils.lookupPath()) + '/node_modules/' + pluginPath);
139 }
1401 if ('function' === typeof plugin) {
1411 plugin = plugin(options);
142 }
143 }
14411 registerPlugin(plugin);
145 },
146
147 initialize: function(config) {
148 // reset
149122 plugins = [];
150122 modes = []; // all registered modes
151122 pluginModes = {}; // map of modes and plugins scoped to the mode
152122 modeAll = []; // plugins that are scoped to all modes
153
154 // load the core plugins
155122 if (!options.ignoreCorePlugins) {
156119 corePlugins.forEach(registerPlugin);
157 }
158
159122 var self = this;
160122 function plugin(plugins) {
161244 if (plugins) {
1627 plugins.forEach(self.use, self);
163 }
164 }
165
166 // load command line plugins
167122 plugin(options.plugins);
168
169 // load lumbar.json plugins
170122 plugin(config.attributes.plugins);
171 },
172
173 loadMixin: function(context, complete) {
17463 runPlugins(context, 'loadMixin', complete, undefined, true);
175 },
176 loadConfig: function(context, complete) {
177121 runPlugins(context, 'loadConfig', complete, undefined, true);
178 },
179 outputConfigs: function(context, complete) {
180229 runPlugins(context, 'outputConfigs', complete, function() {
181 // Default to a one to one mapping for a given {platform, package, module, mode} combo
182227 return [ {} ];
183 });
184 },
185 modeComplete: function(context, complete) {
186177 runPlugins(context, 'modeComplete', complete);
187 },
188 fileName: function(context, complete) {
189246 runPlugins(context, 'fileName', complete);
190 },
191
192 fileFilter: function(context) {
193311 return runPlugins(context, 'fileFilter');
194 },
195 moduleResources: function(context, complete) {
196423 runPlugins(context, 'moduleResources', complete, function() {
197233 var module = context.module;
198233 return (module[context.mode] || []).slice();
199 });
200 },
201 resourceList: function(context, complete) {
202976 runPlugins(context, 'resourceList', complete, function() { return [context.resource]; });
203 },
204
205 file: function(context, complete) {
206139 runPlugins(context, 'file', complete);
207 },
208 module: function(context, complete) {
209265 runPlugins(context, 'module', complete);
210 },
211 resource: function(context, complete) {
212775 runPlugins(context, 'resource', complete, function() { return context.resource; });
213 },
214 modes: function() {
21545 return modes;
216 }
217 };
218};
219

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/coffee-script.js

100%
14
14
0
LineHitsSource
11var CoffeeScript = require('coffee-script'),
2 path = require('path'),
3 fu = require('../fileUtil'),
4 _ = require('underscore');
5
61module.exports = {
7 mode: 'scripts',
8 priority: 50,
9 resource: function(context, next, complete) {
10262 var resource = context.resource;
11262 if (/\.coffee$/.test(resource.src)) {
12
132 next(function(err, resource) {
142 function generator(context, callback) {
15 // Load the source data
162 context.loadResource(resource, function(err, file) {
172 if (err) {
181 return callback(err);
19 }
20
21 // Update the content
221 callback(err, {
23 data: CoffeeScript.compile(file.content.toString()),
24 inputs: file.inputs
25 });
26 });
27 }
28
29 // Include any attributes that may have been defined on the base entry
302 if (!_.isString(resource)) {
312 _.extend(generator, resource);
32 }
332 complete(undefined, generator);
34 });
35 } else {
36260 next(complete);
37 }
38 }
39};
40

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/handlebars.js

90%
88
80
8
LineHitsSource
1/**
2 * Template Plugin : Includes handlebars templates associated with a given file
3 * when said file is imported.
4 *
5 * Config:
6 * root:
7 * templates:
8 * template: Defines the template that is used to output the template in the module. See consts below.
9 * precompile:
10 * Flag/hash that enable precompilation. Truthy will enable precompilation. A hash with
11 * the key name "template" will override the rendering template. (See the template value above.)
12 * cache: Name of the javascript object that templates will be assigned to.
13 * Defaults to `$AppModule.templates` if an app module exists, otherwise `templates`
14 *
15 * Mixins:
16 * The template plugin will mixin any special values directly, giving priority to the local version.
17 *
18 */
191var _ = require('underscore'),
20 handlebars = require('handlebars'),
21 templateUtil = require('../templateUtil');
22
231handlebars.registerHelper('without-extension', function(str) {
241 return str.replace(/\.[a-zA-Z0-9]+$/, '');
25});
26
27
281const DEFAULT_TEMPLATE_TEMPLATE = "/* handsfree : {{{name}}}*/\n{{{templateCache}}}['{{{name}}}'] = {{handlebarsCall}}({{{data}}});\n";
29
301function ensureTemplateTemplates(context, complete) {
3137 if (!context.configCache.templateTemplate) {
3211 var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE;
3311 context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) {
3411 if (err) {
351 complete(err);
36 } else {
3710 context.configCache.templateTemplate = compiled;
3810 complete();
39 }
40 });
41 } else {
4226 complete();
43 }
44}
45
461function loadTemplate(name, resource, context, callback) {
4737 ensureTemplateTemplates(context, function(err) {
4837 if (err) {
491 return callback(err);
50 }
5136 context.fileUtil.readFileArtifact(name, 'template', function(err, cache) {
5236 if (err) {
530 callback(new Error('Failed to load template "' + name + '"\n\t' + err));
540 return;
55 }
56
5736 var artifact = cache.artifact || {},
58 data = artifact.data || cache.data.toString(),
59 attr = context.config.attributes,
60 templates = attr.templates || attr.views || {},
61 appModule = context.config.scopedAppModuleName(context.module),
62 templateCache = (context.config.attributes.templates && context.config.attributes.templates.cache)
63 || context.config.attributes.templateCache
64 || ((appModule ? appModule + '.' : '') + 'templates'),
65 template = context.configCache.templateTemplate;
66
67 // We have the template data, now convert it into the proper format
6836 name = templateUtil.escapeJsString(name);
6936 if (!cache.artifact) {
7015 if (templates.precompile) {
711 var options = context.configCache.precompileTemplates;
721 if (!options) {
731 context.configCache.precompileTemplates = options = _.clone(templates.precompile);
741 if (options.knownHelpers) {
750 options.knownHelpers = options.knownHelpers.reduce(
76 function(value, helper) {
770 value[helper] = true;
780 return value;
79 }, {});
80 }
81 }
821 try {
831 data = handlebars.precompile(data, options);
84 } catch (err) {
850 return callback(err);
86 }
87 } else {
8814 data = "'" + templateUtil.escapeJsString(data) + "'";
89 }
9015 context.fileUtil.setFileArtifact(name, 'template', {data: data, template: template});
91 }
92
93
9436 var mixinRoot = (resource.library && resource.library.root) || '';
9536 if (name.indexOf(mixinRoot) === 0) {
9636 name = name.substring(mixinRoot.length);
97 }
9836 if (templates.root && name.indexOf(templates.root) === 0) {
993 name = name.substring(templates.root.length);
100 }
101
10236 callback(
103 undefined,
104 template({
105 name: name,
106 handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile',
107 templateCache: templateCache,
108 data: data
109 })
110 );
111 });
112 });
113}
114
1151module.exports = {
116 mode: 'scripts',
117 priority: 50,
118
119 loadMixin: function(context, next, complete) {
12063 var mixinTemplates = context.loadedLibrary.templates;
12163 if (mixinTemplates) {
1228 var templates = context.libraries.originalConfig.templates || {},
123 configTemplates = _.clone(context.config.attributes.templates || templates),
124 assigned = false;
125
1268 ['template', 'precompile', 'cache', 'root'].forEach(function(key) {
12732 if (_.has(mixinTemplates, key) && !_.has(templates, key)) {
1286 configTemplates[key] = mixinTemplates[key];
1296 assigned = true;
130 }
131 });
132
1338 if (assigned) {
1343 context.config.attributes.templates = configTemplates;
135 }
136 }
13763 next(complete);
138 },
139
140 resource: function(context, next, complete) {
141218 var resource = context.resource;
142
143218 if (/\.handlebars$/.test(resource.src) || resource.template) {
14425 var loadedTemplates = context.fileCache.loadedTemplates;
14525 if (!loadedTemplates) {
14624 loadedTemplates = context.fileCache.loadedTemplates = {};
147 }
148
14925 var generator = function(buildContext, callback) {
15025 var output = [],
151 inputs = [];
15225 context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) {
15325 if (err) {
1540 callback(err);
1550 return;
156 }
157
15825 function ignore(file) {
159113 return file.dir || loadedTemplates[file.src || file];
160 }
16125 function checkComplete() {
16261 if (inputs.length === files.length) {
163 // Sorting is effectively sorting on the file name due to the name comment in the template
16424 callback(undefined, {
165 inputs: inputs,
166 data: output.sort().join(''),
167 generated: true,
168 noSeparator: true,
169 ignoreWarnings: true
170 });
17124 return true;
172 }
173 }
174
17545 inputs = _.map(files.filter(ignore), function(input) { return input.src || input; });
17625 if (checkComplete()) {
1771 return;
178 }
179
18024 files.forEach(function(file) {
18156 if (ignore(file)) {
18219 return;
183 }
184
18537 var src = file.src || file;
18637 loadedTemplates[src] = true;
18737 loadTemplate(src, resource, context, function(err, data) {
18837 if (err) {
1891 return callback(err);
190 }
191
19236 output.push(data.data || data);
19336 inputs.push(src);
19436 checkComplete();
195 });
196 });
197 });
198 };
19925 generator.sourceFile = resource.src;
20025 complete(undefined, generator);
201 } else {
202193 next(complete);
203 }
204 }
205};
206

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/inline-styles-resources.js

100%
8
8
0
LineHitsSource
11var inlineStyles = require('./inline-styles');
2
31module.exports = {
4 mode: ['scripts', 'styles'],
5 priority: 80,
6
7 moduleResources: function(context, next, complete) {
8341 if (inlineStyles.isInline(context) && context.mode === 'styles') {
9 // Prevent stylesheet output if in inline mode
103 complete(undefined, []);
11338 } else if (inlineStyles.isInline(context)) {
126 next(function(err, scripts) {
136 complete(undefined, scripts.concat(context.module.styles || []));
14 });
15 } else {
16332 next(complete);
17 }
18 }
19};
20

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/inline-styles.js

94%
34
32
2
LineHitsSource
1/**
2 * Inline-Styles Plugin : Include stylesheet in javascript modules
3 *
4 * Config:
5 * root:
6 * styles:
7 * inline: Truthy to inline styles on build.
8 * inlineLoader: Javascript method used to load sheets on the client.
9 *
10 * Mixins:
11 * All fields may be mixed in. In the case of conflicts the local config wins.
12 */
131var _ = require('underscore');
14
151function isInline(context) {
161456 return (context.config.attributes.styles || {}).inline;
17}
18
191module.exports = {
20 isInline: isInline,
21 mode: ['scripts', 'styles'],
22 priority: 10,
23
24 loadMixin: function(context, next, complete) {
2563 var mixinStyles = context.loadedLibrary.styles;
2663 if (mixinStyles) {
2716 var styles = context.libraries.originalConfig.styles || {},
28 configStyles = _.clone(context.config.attributes.styles || styles),
29 assigned = false;
30
3116 ['inline', 'inlineLoader'].forEach(function(key) {
3232 if ((key in mixinStyles) && !(key in styles)) {
336 configStyles[key] = mixinStyles[key];
34
356 assigned = true;
36 }
37 });
38
3916 if (assigned) {
405 context.config.attributes.styles = configStyles;
41 }
42 }
4363 next(complete);
44 },
45
46 outputConfigs: function(context, next, complete) {
47183 if (isInline(context) && context.mode === 'styles') {
48 // Prevent stylesheet output if in inline mode
492 complete(undefined, []);
50 } else {
51181 next(complete);
52 }
53 },
54
55 module: function(context, next, complete) {
56186 next(function(err) {
57186 if (err) {
580 return complete(err);
59 }
60
61186 if (isInline(context)) {
623 context.moduleResources = context.moduleResources.map(function(resource) {
639 if (resource.style || /\.css$/.test(resource.src)) {
643 var generator = function(context, callback) {
653 context.loadResource(resource, function(err, data) {
663 if (err) {
670 return callback(err);
68 }
69
703 var config = context.config,
71 loaderName = config.attributes.styles.inlineLoader || (config.scopedAppModuleName(context.module) + '.loader.loadInlineCSS');
723 callback(err, {
73 data: loaderName + '("'
74 + data.content
75 .replace(/\\/g, '\\')
76 .replace(/\n/g, '\\n')
77 .replace(/"/g, '\\"')
78 + '");\n',
79 inputs: data.inputs,
80 generated: true,
81 noSeparator: true
82 });
83 });
84 };
853 generator.style = true;
863 generator.sourceFile = resource.sourceFile || resource.src;
873 return generator;
88 } else {
896 return resource;
90 }
91 });
92 }
93
94186 complete();
95 });
96 }
97};
98

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/many-to-one-output.js

95%
43
41
2
LineHitsSource
11var _ = require('underscore'),
2 lumbar = require('../lumbar');
3
41function filterDuplicates(context) {
5192 if (context.config.attributes.filterDuplicates === false) {
62 return context.moduleResources;
7 }
8
9190 var paths = {};
10190 return _.filter(context.moduleResources, function(resource) {
11377 if (resource.src) {
12185 var id = (resource.global ? 'global_' : '') + resource.src;
13185 if (paths[id] && !resource.duplicate) {
142 return false;
15 }
16183 paths[id] = true;
17 }
18375 return true;
19 });
20}
21
221function combineResources(context, outputData, callback) {
23162 var resources = context.resources || [];
24162 if (!resources.length) {
2549 return callback();
26 }
27
28113 context.outputFile(function(callback) {
29113 lumbar.combine(
30 context,
31 resources,
32 context.fileName,
33 context.options.minimize && context.mode === 'scripts',
34 context.mode === 'styles',
35 function(err, data) {
36113 if (data) {
37112 _.extend(data, outputData);
38 }
39113 callback(err, data);
40 });
41 },
42 callback);
43}
44
451module.exports = {
46 priority: 1,
47
48 modeComplete: function(context, next, complete) {
49125 next(function(err) {
50125 if (err) {
510 return complete(err);
52 }
53
54125 if (context.combined) {
55 // Build the resources array from each of the modules (Need to maintain proper ordering)
5630 var modules = context.config.moduleList(context.package);
5730 context.resources = [];
5830 modules.forEach(function(module) {
5960 context.resources.push.apply(context.resources, context.combineResources[module]);
60 });
6130 combineResources(context, {}, complete);
62 } else {
6395 complete();
64 }
65 });
66 },
67 module: function(context, next, complete) {
68192 next(function(err) {
69192 if (err) {
700 return complete(err);
71 }
72
73192 if (!context.combined) {
74132 context.resources = filterDuplicates(context);
75132 context.moduleResources = undefined;
76132 combineResources(context, {
77 module: context.module.name
78 },
79 complete);
80 } else {
8160 context.combineResources = context.combineResources || {};
8260 context.combineResources[context.module.name] = filterDuplicates(context);
8360 context.moduleResources = undefined;
8460 complete();
85 }
86 });
87 }
88};
89

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/mixin.js

96%
60
58
2
LineHitsSource
11var _ = require('underscore');
2
31module.exports = {
4 priority: 1,
5
6 loadConfig: function(context, next, complete) {
7118 var modules = context.config.attributes.modules,
8 errored;
9118 _.each(context.libraries.configs, function(library) {
10 // Import any modules that are not overriden in the core file
1159 _.each(library.modules, function(module, key) {
1219 if (!_.has(modules, key)) {
1310 module = modules[key] = _.clone(module);
14
1510 ['scripts', 'styles', 'static', 'routes'].forEach(function(field) {
1640 var value = module[field];
17
18 // Deep(er) clone, updating file references
1940 if (_.isArray(value)) {
2012 module[field] = context.libraries.mapFiles(value, library);
2128 } else if (value) {
220 module[field] = _.clone(value);
23 }
24 });
25 }
26 });
27 });
28
29118 _.each(modules, function(module, name) {
30144 var mixins;
31144 try {
32144 mixins = context.libraries.moduleMixins(module);
33 } catch (err) {
342 errored = true;
352 return complete(new Error('Failed mixins for module "' + name + '": ' + err.message));
36 }
37
38 // Map existing files that have mixin references
39142 try {
40142 ['scripts', 'styles', 'static'].forEach(function(field) {
41424 var list = module[field];
42
43424 if (list) {
44114 module[field] = context.libraries.mapFiles(list);
45 }
46 });
47
48141 _.each(mixins, function(mixin) {
4930 var mixinConfig = mixin.mixinConfig,
50 library = mixin.library;
51
52 // Direct copy for any fields that are not already defined on the object.
5330 _.defaults(module, library.attributes);
54
55 // Merge known array/object types
5630 ['scripts', 'styles', 'static', 'routes'].forEach(function(field) {
57120 mergeValues(module, field, library, mixinConfig, context);
58 });
59 });
60 } catch (err) {
611 errored = true;
621 return complete(err);
63 }
64 });
65
66 // Remove suppressed modules completely
67118 _.each(_.keys(modules), function(name) {
68144 if (!modules[name]) {
691 delete modules[name];
70 }
71 });
72
73118 if (!errored) {
74115 next(complete);
75 }
76 }
77};
78
791function firstLocal(collection) {
8042 for (var i = 0, len = collection.length; i < len; i++) {
8157 if (!collection[i].global) {
8242 return i;
83 }
84 }
850 return i;
86}
87
881function mergeValues(module, field, library, mixinConfig, context) {
89120 var value = module[field],
90 mixinValue = library.attributes[field];
91
92120 if (!value) {
9381 return;
94 }
95
9639 if (value === mixinValue) {
97 // Clone any direct copy entries from a mixin
9812 if (_.isArray(value)) {
9910 module[field] = context.libraries.mapFiles(value, library, mixinConfig);
100 } else {
1012 module[field] = _.clone(value);
102 }
10327 } else if (!_.isArray(value)) {
1045 _.defaults(value, mixinValue);
10522 } else if (mixinValue) {
10621 mixinValue = context.libraries.mapFiles(mixinValue, library, mixinConfig);
107
10821 var mixinFirstLocal = firstLocal(mixinValue),
109 moduleFirstLocal = firstLocal(value);
110
11121 if (mixinFirstLocal) {
1124 value.unshift.apply(value, mixinValue.slice(0, mixinFirstLocal));
113 }
11421 if (mixinFirstLocal < mixinValue.length) {
11521 var locals = mixinValue.slice(mixinFirstLocal);
11621 locals.unshift(mixinFirstLocal + moduleFirstLocal, 0);
11721 value.splice.apply(value, locals);
118 }
119 }
120}
121

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/module-map.js

87%
124
108
16
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 handlebars = require('handlebars'),
4 fs = require('fs'),
5 path = require('path'),
6 dirname = path.dirname;
7
81var moduleMapTemplate;
9
101function getModuleMapTemplate() {
1117 if (!moduleMapTemplate) {
121 moduleMapTemplate = handlebars.compile(fs.readFileSync(__dirname + '/module-map.handlebars').toString());
13 }
1417 return moduleMapTemplate;
15}
16
17// Force template load before EMFILE may be an issue
181getModuleMapTemplate();
19
201function loadModuleMap(map, mapper, callback) {
2116 var moduleMapTemplate = getModuleMapTemplate();
22
23 // This bit of voodoo forces uniform ordering for the output under node. This is used primarily for
24 // testing purposes.
2516 map = (function orderObject(map) {
2694 var ret = _.isArray(map) ? [] : {};
2794 _.keys(map).sort().forEach(function(key) {
28167 var value = map[key];
29167 ret[key] = _.isObject(value) ? orderObject(value) : value;
30 });
3194 return ret;
32 })(map);
33
3416 callback(
35 undefined,
36 moduleMapTemplate({
37 moduleMapper: mapper,
38 map: JSON.stringify(map)
39 })
40 );
41}
42
431function buildMap(context, callback) {
4434 if (context.combined) {
4515 moduleConfig(context, undefined, function(err, config, prefix) {
4615 callback(err, { base: config }, prefix);
47 });
48 } else {
4919 var attr = context.config.attributes || {},
50 app = attr.application || {},
51 modules = context.config.moduleList(context.package);
52
5319 var map = {modules: {}, routes: {}},
54 commonPrefix;
55
5619 async.forEach(modules, function(module, callback) {
5726 moduleConfig(context, module, function(err, config, prefix) {
5826 if (err) {
590 return callback(err);
60 }
61
6226 if (app.module === module) {
634 map.base = config;
64 } else {
6522 map.modules[module] = config;
66
6722 var routes = context.config.routeList(module);
6822 _.each(routes, function(value, route) {
6917 map.routes[route] = module;
70 });
71 }
7226 commonPrefix = findPrefix(prefix, commonPrefix);
73
7426 callback();
75 });
76 },
77 function(err) {
7819 callback(err, map, commonPrefix);
79 });
80 }
81}
82
831function stripPrefix(map, prefix) {
8416 if (!prefix) {
8515 return;
86 }
87
881 function stripModule(module) {
890 if (module.js) {
900 module.js = stripList(module.js);
91 }
920 if (module.css) {
930 module.css = stripList(module.css);
94 }
95 }
961 function stripList(list) {
970 if (_.isArray(list)) {
980 return list.map(stripEntry);
99 } else {
1000 return stripEntry(list);
101 }
102 }
1031 function stripEntry(entry) {
1040 if (entry.href) {
1050 entry.href = entry.href.substring(prefix.length);
1060 return entry;
107 } else {
1080 return entry.substring(prefix.length);
109 }
110 }
1111 if (map.base) {
1120 stripModule(map.base);
113 }
1141 if (map.modules) {
1150 _.each(map.modules, stripModule);
116 }
117}
1181function moduleConfig(context, module, callback) {
11941 var ret = {},
120 commonPrefix,
121 preload = module && context.config.module(module).preload,
122 depends = module && context.config.module(module).depends;
12341 if (preload) {
1241 ret.preload = preload;
125 }
12641 if (depends) {
1274 ret.depends = depends;
128 }
12941 async.forEach([{key: 'js', mode: 'scripts'}, {key: 'css', mode: 'styles'}], function(obj, callback) {
13082 fileList(context, obj.mode, module, function(err, list, prefix) {
13182 ret[obj.key] = list;
13282 commonPrefix = findPrefix(prefix, commonPrefix);
13382 callback(err);
134 });
135 },
136 function(err) {
13741 callback(err, ret, commonPrefix);
138 });
139}
1401function fileList(context, mode, module, callback) {
141 // Check to see if we even have this type of resource
14282 var modules = !context.combined ? [ module ] : context.config.moduleList(context.package);
14382 async.some(modules, function(module, callback) {
144112 var resourceContext = context.clone();
145112 resourceContext.mode = mode;
146112 resourceContext.module = context.config.module(module);
147112 resourceContext.isModuleMap = true;
148
149112 resourceContext.plugins.moduleResources(resourceContext, function(err, resources) {
150112 callback((resources || []).length);
151 });
152 },
153 function(hasResource) {
15482 if (!hasResource) {
1559 return callback();
156 }
157
158 // Output the config
15973 context.fileNamesForModule(mode, module, function(err, configs) {
16073 if (err) {
1610 return callback(err);
162 }
163
16473 var prefix;
165172 configs = configs.filter(function(config) { return !config.server; });
16699 configs = configs.sort(function(a, b) { return a.pixelDensity - b.pixelDensity; });
16773 configs = configs.map(function(config, i) {
16899 var path = config.fileName.path,
169 ret = path + '.' + config.fileName.extension;
170
17199 if (config.pixelDensity) {
17261 ret = { href: ret };
17361 if (0 < i) {
17426 ret.minRatio = configs[i - 1].pixelDensity + (config.pixelDensity - configs[i - 1].pixelDensity) / 2;
175 }
17661 if (i < configs.length - 1) {
17726 ret.maxRatio = config.pixelDensity + (configs[i + 1].pixelDensity - config.pixelDensity) / 2;
178 }
179 }
180
181 // Update the prefix tracker
18299 prefix = findPrefix(path, prefix);
183
18499 return ret;
185 });
186
18773 var ret;
18873 if (configs.length === 1) {
18948 ret = configs[0];
19025 } else if (configs.length) {
19125 ret = configs;
192 }
19373 callback(undefined, ret, prefix);
194 });
195 });
196}
197
1981function findPrefix(path, prefix) {
199 /*jshint eqnull:true*/
200207 if (path == null) {
2019 return prefix;
202 }
203198 if (prefix == null) {
204 // Ensure that we get 'x' for strings of type 'x/'
205133 prefix = dirname(path + 'a') + '/';
206 }
207198 for (var i = 0, len = prefix.length; i < len; i++) {
208133 if (path.charAt(i) !== prefix.charAt(i)) {
209133 return prefix.substring(0, i);
210 }
211 }
21265 return prefix;
213}
214
2151module.exports = {
216 mode: 'scripts',
217 priority: 50,
218
219 buildMap: buildMap,
220
221 resource: function(context, next, complete) {
222241 var config = context.config;
223
224241 if (context.resource['module-map']) {
22521 var buildModuleMap = function(context, callback) {
22616 module.exports.buildMap(context, function(err, map, prefix) {
22716 if (err) {
2280 callback(err);
229 } else {
23016 var moduleMap = config.attributes.moduleMap || 'module.exports.moduleMap';
23116 stripPrefix(map, prefix);
23216 loadModuleMap(map, moduleMap, function(err, data) {
23316 callback(err, data && {data: data, generated: true, noSeparator: true, ignoreWarnings: true});
234 });
235 }
236 });
237 };
23821 buildModuleMap.sourceFile = undefined;
23921 complete(undefined, buildModuleMap);
240 } else {
241220 next(complete);
242 }
243 }
244};
245

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/package-config.js

90%
21
19
2
LineHitsSource
11var handlebars = require('handlebars');
2
31const DEFAULT_CONFIG_TEMPLATE = "{{{name}}} = {{{data}}};\n";
41var packageConfigTemplate = handlebars.compile(DEFAULT_CONFIG_TEMPLATE);
5
61function loadPackageConfig(name, configFile, fileUtil, callback) {
716 if (!configFile) {
81 return callback(new Error('package_config.json specified without file being set'));
9 }
10
1115 fileUtil.readFile(configFile, function(err, data) {
1215 if (err) {
130 callback(new Error('Failed to load package config "' + configFile + '"\n\t' + err));
140 return;
15 }
16
1715 callback(
18 undefined,
19 packageConfigTemplate({
20 name: name,
21 data: data
22 })
23 );
24 });
25}
26
271module.exports = {
28 mode: 'scripts',
29 priority: 50,
30
31 resource: function(context, next, complete) {
32279 var resource = context.resource;
33
34279 if (resource['package-config']) {
3517 var packageConfigGen = function(context, callback) {
3616 var config = context.config,
37 options = context.options,
38 packageConfig = config.attributes.packageConfig || 'module.exports.config';
39
4016 loadPackageConfig(packageConfig, options.packageConfigFile, context.fileUtil, function(err, data) {
4116 callback(err, data && {data: data, inputs: [options.packageConfigFile], generated: true, noSeparator: true});
42 });
43 };
4417 packageConfigGen.sourceFile = undefined;
4517 complete(undefined, packageConfigGen);
46 } else {
47262 next(complete);
48 }
49 }
50};
51

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/router.js

95%
22
21
1
LineHitsSource
11var handlebars = require('handlebars');
2
31const TEMPLATE = '/* router : {{{name}}} */\nmodule.name = "{{{name}}}";\nmodule.routes = {{{routes}}};\n';
41var routerTemplate = handlebars.compile(TEMPLATE);
5
61function loadRouter(context, name, routes, callback) {
714 callback(
8 undefined,
9 routerTemplate({
10 name: name,
11 routes: JSON.stringify(routes)
12 })
13 );
14}
15
161module.exports = {
17 mode: 'scripts',
18 priority: 50,
19
20 moduleResources: function(context, next, complete) {
21187 next(function(err, ret) {
22187 if (err) {
230 return complete(err);
24 }
25
26 // Generate the router if we have the info for it
27187 var module = context.module;
28187 if (module.routes) {
2947 ret.unshift({ routes: module.routes });
30 }
31
32187 complete(undefined, ret);
33 });
34 },
35 resource: function(context, next, complete) {
36262 var resource = context.resource,
37 module = context.module.name;
38
39262 if (resource.routes) {
4021 var routerGen = function(context, callback) {
4114 loadRouter(context, module, resource.routes, function(err, data) {
4214 callback(err, data && {data: data, generated: true, noSeparator: true});
43 });
44 };
4521 routerGen.moduleStart = true;
4621 routerGen.sourceFile = undefined;
4721 complete(undefined, routerGen);
48 } else {
49241 next(complete);
50 }
51 }
52};
53

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/scope.js

94%
107
101
6
LineHitsSource
1/**
2 * Scope Plugin : Wrap javascript units in module scopes.
3 *
4 * Config:
5 * root:
6 * scope:
7 * scope: Size of the smallest module scope. May be: 'module', 'resource', 'none'
8 * template: Template used override the default module logic.
9 * This may be an inline handlebars template or a reference to a handlebars file.
10 * Available fields:
11 * scope : Name of the javascript module
12 * isTopNamespace : Truthy if the current module is a top level namespace
13 * appName : Name of the application object
14 * yield : Location that the embedded javascript will be inserted
15 * aliases : Key value mapping of objects that will be imported into the module locally.
16 * This is useful for allowing minimization of commonly used objects such as the
17 * application object or common libraries.
18 *
19 * root.scope may be set to the scope values as a shorthand.
20 *
21 * Mixins:
22 * All fields may be mixed in. Template file references are converted to mixin space. The alias
23 * field will be mixed in per-key with the local definition taking priority.
24 */
251var _ = require('underscore');
26
271function getScope(attr) {
28407 return (attr.scope && attr.scope.scope) || attr.scope;
29}
301function toObj(obj) {
3175 return _.isString(obj) ? {scope: obj} : obj;
32}
33
341function generator(string) {
35166 var ret = function(context, callback) { callback(undefined, {data: string, generated: true, noSeparator: true}); };
3696 ret.stringValue = string;
3796 ret.sourceFile = undefined;
3896 ret.ignoreWarnings = true;
3996 return ret;
40}
41
421var scopeTemplateDelimiter = /\{?\{\{yield\}\}\}?/;
43
441function ensureModuleTemplates(context, complete) {
4546 if (!context.configCache.moduleTemplate) {
4629 var template = context.config.attributes.scope && context.config.attributes.scope.template;
4729 if (!template) {
4818 template = __dirname + '/scope-module.handlebars';
49 }
50
5129 context.fileUtil.loadTemplate(template, scopeTemplateDelimiter, function(err, templates) {
5229 if (err) {
530 complete(err);
54 } else {
5529 context.configCache.moduleTemplate = {
56 start: templates[0],
57 end: templates[1]
58 };
5929 complete();
60 }
61 });
62 } else {
6317 complete();
64 }
65}
66
671function wrapResources(resources, context) {
6846 var cache = context.moduleCache;
6946 if (!cache.scopeName) {
7046 var app = context.config.attributes.application,
71 appName = app && app.name;
72
7346 if (!appName || context.module.topLevelName || context.config.isAppModule(context.module)) {
7432 cache.isTopNamespace = true;
7532 cache.scopeName = context.module.topLevelName || appName || context.module.name;
76 } else {
7714 cache.scopeName = appName + "['" + context.module.name + "']";
78 }
7946 cache.appName = appName;
80 }
81
82 // Wrap the module content in a javascript module
8346 if (resources.length) {
8446 function isModule(reference) {
8526 var stripOperators = /['"\]]/g;
8626 return reference === cache.scopeName
87 || (!cache.isTopNamespace
88 && reference.replace(stripOperators, '').substr(-context.module.name.length) === context.module.name);
89 }
90
9146 var scope = context.config.attributes.scope || {},
92
93 // Call args calculation
94 aliasesHash = context.module.aliases === false ? {} : _.extend({}, scope.aliases, context.module.aliases),
95 aliases = _.pairs(aliasesHash),
9616 aliases = _.filter(aliases, function(alias) { return alias[1]; }),
9713 externals = _.filter(aliases, function(alias) { return !isModule(alias[1]); }),
98 aliasVars = _.pluck(externals, '0'),
99 callSpec = _.pluck(externals, '1'),
100
101 // Internal scope calculation
10213 internals = _.filter(aliases, function(alias) { return isModule(alias[1]); }),
103 internalVars = _.pluck(internals, '0'),
104 internalScope = '';
105
10646 callSpec.unshift('this');
10746 if (cache.isTopNamespace) {
10832 internalVars.unshift(cache.scopeName);
109 } else {
11014 internalScope += cache.scopeName + ' = exports;';
111 }
11284 internalVars = _.map(internalVars, function(name) { return name + ' = exports'; });
11346 if (internalVars.length) {
11433 internalScope += 'var ' + internalVars.join(', ') + ';';
115 }
116
11746 var scopeDecl = '';
11846 if (context.moduleCache.isTopNamespace) {
119 // Insert the package declaration
12032 scopeDecl = 'var ' + context.moduleCache.scopeName + ';';
121 }
12246 var templateContext = {
123 isTopNamespace: cache.isTopNamespace,
124 name: cache.appName,
125 scopeDecl: scopeDecl,
126 scope: cache.scopeName,
127 aliasVars: aliasVars.join(', '),
128 internalScope: internalScope,
129 callSpec: callSpec.join(', ')
130 };
131
13246 resources.unshift(generator(context.configCache.moduleTemplate.start(templateContext)));
13346 resources.push(generator(context.configCache.moduleTemplate.end(templateContext)));
134 }
13546 return resources;
136}
137
1381module.exports = {
139 mode: 'scripts',
140 priority: 50,
141
142 loadMixin: function(context, next, complete) {
14363 var mixinScope = toObj(context.loadedLibrary.scope);
14463 if (mixinScope) {
1456 var scope = toObj(context.libraries.originalConfig.scope || {}),
146 configScope = toObj(_.clone(context.config.attributes.scope || scope)),
147 assigned = false;
148
1496 if (('scope' in mixinScope) && !('scope' in scope)) {
1502 configScope.scope = mixinScope.scope;
151
1522 assigned = true;
153 }
154
1556 if (('template' in mixinScope) && !('template' in scope)) {
1563 configScope.template = (context.loadedLibrary.root || '') + mixinScope.template;
157
1583 assigned = true;
159 }
160
1616 if (context.libraries.mergeHash('aliases', scope, mixinScope, configScope)) {
1623 assigned = true;
163 }
164
1656 if (assigned) {
1664 context.config.attributes.scope = configScope;
167 }
168 }
16963 next(complete);
170 },
171 loadConfig: function(context, next, complete) {
172115 var modules = context.config.attributes.modules;
173
174115 try {
175115 _.each(modules, function(module) {
176138 var mixins = context.libraries.moduleMixins(module);
177
178138 _.each(mixins, function(mixin) {
17930 context.libraries.mergeHash('aliases', module, mixin.library.attributes, module);
180 });
181 });
182 } catch (err) {
1830 return complete(err);
184 }
185
186115 next(complete);
187 },
188
189 resourceList: function(context, next, complete) {
190306 next(function(err, resources) {
191306 if (err) {
1920 return complete(err);
193 }
194
195306 if (getScope(context.config.attributes) === 'resource'
196 && !context.resource.global
197 && !context.resource.dir) {
1982 resources.unshift(generator('(function() {\n'));
1992 resources.push(generator('}).call(this);\n'));
200 }
201306 complete(undefined, resources);
202 });
203 },
204
205 module: function(context, next, complete) {
206101 next(function(err) {
207101 if (err) {
2080 return complete(err);
209 }
210
211101 var resources = context.moduleResources,
212 scope = getScope(context.config.attributes);
213
214101 if (resources.length && scope !== 'none') {
21546 ensureModuleTemplates(context, function(err) {
21646 if (err) {
2170 complete(err);
218 } else {
219 // Split up globals and non-globals
22046 var globals = [],
221 children = [],
222 moduleStart = [];
22346 for (var i = 0; i < resources.length; i++) {
224158 var resource = resources[i];
225158 if (resource.moduleStart) {
22613 moduleStart.push(resource);
227145 } else if (!resource.global) {
228130 children.push(resource);
229 } else {
23015 if (children.length) {
2310 throw new Error('Scoped files may not appear before global files.');
232 }
23315 globals.push(resource);
234 }
235 }
236
23746 children = moduleStart.concat(children);
23846 globals.push.apply(globals, wrapResources(children, context, complete));
239
24046 context.moduleResources = globals;
24146 complete();
242 }
243 });
244 } else {
24555 complete();
246 }
247 });
248 }
249};
250
251

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/scripts-output.js

100%
2
2
0
LineHitsSource
11var _ = require('underscore'),
2 manyToOne = require('./many-to-one-output');
3
41module.exports = _.extend({ mode: 'scripts' }, manyToOne);
5

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/scripts.js

100%
5
5
0
LineHitsSource
11module.exports = {
2 mode: 'scripts',
3 priority: 99,
4
5 fileFilter: function(context, next) {
6131 return /\.(js|json)$/;
7 },
8
9 fileName: function(context, next, complete) {
10107 complete(undefined, {path: context.baseName, extension: 'js'});
11 },
12
13 moduleResources: function(context, next, complete) {
14187 var module = context.module;
15187 complete(undefined, (module.scripts || module.files || (module.slice && module) || []).slice());
16 }
17};
18

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/server-scripts.js

8%
23
2
21
LineHitsSource
11var _ = require('underscore');
2
31module.exports = {
4 mode: 'scripts',
5 priority: 98, // Just below the core scripts plugin....
6
7 fileFilter: function(context, next) {
80 return /\.(js|json)$/;
9 },
10
11 outputConfigs: function(context, next, complete) {
120 next(function(err, files) {
130 if (err) {
140 return complete(err);
15 }
16
17 // Permutation of other configs and ours
180 var ret = [];
190 files.forEach(function(fileConfig) {
200 [true, false].forEach(function(server) {
210 var config = _.clone(fileConfig);
220 config.server = server;
230 ret.push(config);
24 });
25 });
260 complete(undefined, ret);
27 });
28 },
29
30 fileName: function(context, next, complete) {
310 next(function(err, ret) {
320 if (ret && context.fileConfig.server) {
330 ret.path += '-server';
34 }
350 complete(err, ret);
36 });
37 },
38
39 moduleResources: function(context, next, complete) {
400 var module = context.module;
41
420 var files = [];
430 (module.server || module.scripts).forEach(function(script) {
440 if (!_.has(script, 'server') || script.server === context.fileConfig.server) {
450 files.push(script);
46 }
47 });
48
490 complete(undefined, files);
50 }
51};
52

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/static-output.js

88%
17
15
2
LineHitsSource
11var async = require('async');
2
31module.exports = {
4 mode: 'static',
5 priority: 1,
6
7 module: function(context, next, complete) {
874 next(function(err) {
974 async.forEach(context.moduleResources, function(resource, callback) {
1028 var fileContext = context.clone();
1128 fileContext.resource = resource;
12
13 // Filter out dir entries
1428 if (resource.dir) {
152 return callback();
16 }
17
1826 fileContext.outputFile(function(callback) {
1926 var fileInfo = fileContext.loadResource(resource, function(err, data) {
2026 if (err || !data || !data.content) {
210 return callback(err);
22 }
23
2426 var ret = {
25 fileName: fileContext.fileName,
26 inputs: fileInfo.inputs || [ fileInfo.name ],
27 module: context.module.name,
28 resource: resource
29 };
30
3126 context.fileUtil.writeFile(fileContext.fileName, data.content, function(err) {
3226 if (err) {
330 err = new Error('Static output "' + fileContext.fileName + '" failed\n\t' + err);
34 }
35
3626 callback(err, ret);
37 });
38 });
39 },
40 callback);
41 },
42 complete);
43 });
44 }
45};
46

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/static.js

92%
25
23
2
LineHitsSource
11var _ = require('underscore');
2
3/*
4 * Replace variables with actual values
5 */
61function replaceVariables(str, context) {
778 return str.replace(/\#{platform}/, context.platform);
8}
9
10/*
11 * Make sure the directory name has a trailing slash
12 */
131function normalizeDirName(dirName) {
142 if (dirName.match(/\/$/)) {
150 return dirName;
16 }
172 return dirName + '/';
18}
19
201module.exports = {
21 mode: 'static',
22 priority: 99,
23
24 fileName: function(context, next, complete) {
2526 var resource = context.resource,
26 src = resource.src || resource.sourceFile,
27 dir = resource.dir;
28
2926 var root = '';
30
3126 if (resource.srcDir && resource.dest) {
32 // srcDir is some prefix of src - we want to append the remaining part or src to dest
332 src = src.substring(resource.srcDir.length + 1);
342 root += normalizeDirName(resource.dest);
3524 } else if (resource.dest) {
3619 src = resource.dest;
37 }
38
3926 root = replaceVariables(root, context);
4026 src = replaceVariables(src, context);
41
4226 var components = /(.*?)(?:\.([^.]+))?$/.exec(src);
4326 complete(undefined, {root: resource.root, path: root + components[1], extension: components[2]});
44 },
45
46 resource: function(context, next, complete) {
4728 next(function(err, resource) {
4828 if (_.isString(resource)) {
490 resource = replaceVariables(resource, context);
5028 } else if (resource.src) {
5126 resource.src = replaceVariables(resource.src, context);
52 }
5328 complete(undefined, resource);
54 });
55 }
56};
57

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/styles-output.js

100%
2
2
0
LineHitsSource
11var _ = require('underscore'),
2 manyToOne = require('./many-to-one-output');
3
41module.exports = _.extend({ mode: 'styles' }, manyToOne);
5

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/styles.js

100%
2
2
0
LineHitsSource
11module.exports = {
2 mode: 'styles',
3 priority: 99,
4
5 fileName: function(context, next, complete) {
6113 complete(undefined, {path: context.baseName, extension: 'css'});
7 }
8};
9

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/stylus-config.js

94%
39
37
2
LineHitsSource
11var _ = require('underscore'),
2 async = require('async');
3
41module.exports = {
5 mode: ['scripts', 'styles'],
6 priority: 25,
7
8 loadMixin: function(context, next, complete) {
963 var mixinStyles = context.loadedLibrary.styles;
1063 if (mixinStyles) {
1116 var styles = context.libraries.originalConfig.styles || {},
12 configStyles = _.clone(context.config.attributes.styles || styles),
13 assigned = false;
14
1516 ['configObject'].forEach(function(key) {
1616 if ((key in mixinStyles) && !(key in styles)) {
171 configStyles[key] = mixinStyles[key];
18
191 assigned = true;
20 }
21 });
22
2316 if (context.libraries.mergeFiles('config', styles, mixinStyles, configStyles, context.loadedLibrary)) {
244 assigned = true;
25 }
26
2716 if (assigned) {
284 context.config.attributes.styles = configStyles;
29 }
30 }
3163 next(complete);
32 },
33
34 resource: function(context, next, complete) {
35405 if (context.resource['stylus-config']) {
363 var configGenerator = function(context, callback) {
37 // TODO : Load and output the JSON config options
38 // We can use normal JSON.parse here as stylus uses this -> we can call extend as part of the build
393 var styles = context.config.attributes.styles || {},
40 configFiles = styles.config || [],
41 stylusConfig = styles.configObject || 'module.exports.stylusConfig';
42
439 configFiles = _.map(configFiles, function(config) { return config.src || config; });
44
453 async.map(configFiles,
46 function(config, callback) {
476 context.fileUtil.readFile(config, function(err, data) {
486 callback(err, data);
49 });
50 },
51 function(err, data) {
523 if (data) {
533 try {
543 var config = _.reduce(data, function(config, json) {
556 return _.extend(config, JSON.parse(json));
56 }, {});
573 data = {data: stylusConfig + ' = ' + JSON.stringify(config) + ';\n', inputs: configFiles, noSeparator: true};
58 } catch (parseError) {
59 // TODO : Better error handling here?
600 err = parseError;
610 data = undefined;
62 }
63 }
643 callback(err, data);
65 });
66 };
673 configGenerator.sourceFile = undefined;
683 complete(undefined, configGenerator);
69 } else {
70402 next(complete);
71 }
72 },
73
74 module: function(context, next, complete) {
75192 next(function() {
76192 var styles = context.config.attributes.styles || {},
77 config = styles.config || [];
78
79192 if (config.length) {
8059 _.each(context.moduleResources, function(resource) {
81218 if (resource.stylus) {
8229 resource.plugins.push({
83 plugin: __dirname + '/stylus-config-worker',
84 data: config
85 });
86 }
87 });
88 }
89
90192 complete();
91 });
92 }
93};
94

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/stylus.js

95%
124
118
6
LineHitsSource
1/**
2 * Stylus Plugin : Compile stylus files.
3 *
4 * Config:
5 * root:
6 * styles:
7 * includes: Array of paths to add to stylus includes.
8 * pixelDensity: Defines the pixel densities generated for each plaform.
9 * urlSizeLimit: Maximum file size to inline. Passed to stylus-images plugin
10 * copyFiles: Boolean specifying if non-inlined url references should be compied
11 * To the build directly. Passed to stylus-images plugin.
12 * styleRoot: Project path to resolve files from.
13 * useNib: Truthy to include nib in the project build
14 *
15 * Mixins:
16 * All fields may be mixed in. In the case of conflicts the local config wins for simple values and
17 * for arrays the content will be merged in order. pixelDensity is mixed in at the platform definition
18 * level. File references are converted to mixin space.
19 *
20 * styleRoot is used locally for file lookup when compiling the mixin content.
21 */
221var _ = require('underscore'),
23 ChildPool = require('../child-pool'),
24 inlineStyles = require('./inline-styles'),
25 path = require('path'),
26 normalize = path.normalize,
27 fu = require('../fileUtil');
28
29// Forward cache resets to any workers
301fu.on('cache:reset', function(path) {
31233 worker.sendAll({type: 'cache:reset', path: path});
32});
33
341var worker = new ChildPool(__dirname + '/stylus-worker');
35
361function generateSource(context, options, styleConfig) {
3731 var includes = (styleConfig.includes || []).concat(options.files),
38 module = options.module;
39
4031 var nibLocation = includes.indexOf('nib'),
41 useNib;
4231 if (styleConfig.useNib) {
4326 useNib = true;
4426 includes.unshift('nib');
455 } else if (nibLocation >= 0) {
46 // Special case nib handling to maintain backwards compatibility
47 // WARN: This may be deprecated in future releases
480 useNib = true;
490 includes.splice(nibLocation, 1);
50 }
51
5231 var declare = context.config.platformList().map(function(platform) {
5366 return '$' + platform + ' = ' + (platform === context.platform);
54 }).join('\n') + '\n';
55
5631 var mixins = [],
57 mixinLUT = {};
58
5931 var source = declare + includes.map(function(include) {
60103 var source = include.library;
61103 var statement = '@import ("' + (include.originalSrc || include.src || include) + '")\n';
62103 if (source) {
638 var name = '',
64 root = (source.parent || source).root || '',
65 stylusRoot = ((source.parent || source).styles || {}).styleRoot,
66 library = (source.parent || source).name || '';
678 if (source.parent) {
687 name = source.name || '';
69 }
708 var mixinName = name + '_' + library;
71
728 if (!mixinLUT[mixinName]) {
735 var mixinDecl = context.libraries.findDecl(module.mixins, {name: name, library: library}),
74 overrides = mixinDecl && mixinDecl.overrides;
75
765 mixins.push({
77 root: normalize(root),
78 stylusRoot: stylusRoot && normalize(stylusRoot),
79 overrides: overrides
80 });
815 mixinLUT[mixinName] = mixins.length-1;
82 }
838 mixinName = mixinLUT[mixinName];
84
858 return 'push-mixin("' + mixinName + '")\n'
86 + statement
87 + 'pop-mixin()\n';
88 } else {
8995 return statement;
90 }
91 }).join('');
92
9331 return {
94 useNib: useNib,
95 source: source,
96 mixins: mixins
97 };
98}
99
1001function compile(options, callback) {
10131 var context = options.context,
102
103 styleConfig = context.config.attributes.styles || {};
104
10531 var loadPrefix = context.config.loadPrefix(),
106 externalPrefix;
10731 if (loadPrefix) {
1089 externalPrefix = loadPrefix + (context.buildPath.indexOf('/') >= 0 ? path.dirname(context.buildPath) + '/' : '');
109 }
110
11131 var imageOptions = {
112 outdir: path.dirname(context.fileName),
113 resolutions: context.modeCache.pixelDensity,
114 limit: styleConfig.urlSizeLimit,
115 copyFiles: styleConfig.copyFiles,
116 externalPrefix: externalPrefix
117 };
118
11931 var source = generateSource(context, options, styleConfig);
120
12131 context.fileUtil.ensureDirs(context.fileName, function(err) {
12231 if (err) {
1230 return callback(err);
124 }
125
12631 worker.send({
127 plugins: options.plugins,
128
129 useNib: source.useNib,
130 imageOptions: imageOptions,
131
132 filename: options.filename,
133 minimize: context.options.minimize,
134
135 source: source.source,
136 mixins: source.mixins,
137
138 lookupPath: context.fileUtil.lookupPath(),
139 styleRoot: styleConfig.styleRoot && context.fileUtil.resolvePath(styleConfig.styleRoot)
140 },
141 callback);
142 });
143}
144
1451module.exports = {
146 // scripts mode is used also to support inline styles
147 mode: ['styles', 'scripts'],
148 priority: 50,
149
150 loadMixin: function(context, next, complete) {
15163 var mixinStyles = context.loadedLibrary.styles;
15263 if (mixinStyles) {
15316 var styles = context.libraries.originalConfig.styles || {},
154 configStyles = _.clone(context.config.attributes.styles || styles),
155 assigned = false;
156
15716 ['urlSizeLimit', 'copyFiles', 'useNib'].forEach(function(key) {
15848 if ((key in mixinStyles) && !(key in styles)) {
1596 configStyles[key] = mixinStyles[key];
160
1616 assigned = true;
162 }
163 });
164
16516 if (context.libraries.mergeFiles('includes', styles, mixinStyles, configStyles, context.loadedLibrary)) {
1664 assigned = true;
167 }
168
16916 if (context.libraries.mergeHash('pixelDensity', styles, mixinStyles, configStyles)) {
1703 assigned = true;
171 }
172
17316 if (assigned) {
1746 context.config.attributes.styles = configStyles;
175 }
176 }
17763 next(complete);
178 },
179
180 outputConfigs: function(context, next, complete) {
181184 if (!inlineStyles.isInline(context) && context.mode !== 'styles') {
18289 return next(complete);
183 }
184
18595 next(function(err, files) {
18695 if (err) {
1870 return complete(err);
188 }
189
19095 var ret = [],
191 styleConfig = context.config.attributes.styles || {},
192 pixelDensity = styleConfig.pixelDensity || {};
19395 if (context.platform) {
19474 pixelDensity = pixelDensity[context.platform] || pixelDensity;
195 }
19695 if (!_.isArray(pixelDensity)) {
19758 pixelDensity = [ 1 ];
198 }
19995 context.modeCache.pixelDensity = pixelDensity;
200
201 // Permutation of other configs and ours
20295 var primary = true;
20395 files.forEach(function(fileConfig) {
20495 pixelDensity.forEach(function(density) {
205133 var config = _.clone(fileConfig);
206133 config.pixelDensity = density;
207133 config.isPrimary = primary;
208133 primary = false;
209133 ret.push(config);
210 });
211 });
21295 complete(undefined, ret);
213 });
214 },
215
216 fileName: function(context, next, complete) {
217224 if (!inlineStyles.isInline(context) && context.mode !== 'styles') {
218101 return next(complete);
219 }
220
221123 next(function(err, ret) {
222123 if (ret && context.fileConfig.pixelDensity !== 1) {
22340 ret.path += '@' + context.fileConfig.pixelDensity + 'x';
224 }
225123 complete(err, ret);
226 });
227 },
228
229 module: function(moduleContext, next, complete) {
230194 next(function(err) {
231 /*jshint eqnull: true */
232194 if (err) {
2330 return complete(err);
234 }
235
236194 function mergeResources(start) {
23749 var generator = function(context, callback) {
23849 function response(data, density) {
23949 if (data) {
24048 return {
241 data: data.data[density || 1],
242 inputs: data.inputs,
243 noSeparator: true
244 };
245 }
246 }
247
24849 var filename = generator.filename;
249
250 // We only want to call stylus once which will generate the css for all of the
251 // resolutions we support on this platform. This ugly bit of code make sure that
252 // we properly handle all of that loading states that can come into play under these
253 // circumstances while still adhering to the output models prescribed by lumbar.
25449 var queue = context.modeCache['stylus_' + filename];
25549 if (_.isArray(queue)) {
256 // We are currently executing
25718 queue.push({density: context.fileConfig.pixelDensity, callback: callback});
25831 } else if (_.isObject(queue)) {
259 // We already have data
2600 callback(undefined, response(queue, context.fileConfig.pixelDensity));
261 } else {
262 // We need to kick of a stylus build
26331 queue = context.modeCache['stylus_' + filename] = [
264 {density: context.fileConfig.pixelDensity, callback: callback}
265 ];
26631 var options = {
267 filename: filename,
268 files: generator.stylusFiles,
269
270 context: context,
271 module: moduleContext.module, // To play nicely with combined mode
272 plugins: generator.plugins
273 };
27431 compile(options, function(err, data) {
27531 if (err) {
2761 data = undefined;
277 }
27831 _.each(queue, function(callback) {
27949 callback.callback(err, response(data, callback.density));
280 });
28131 context.modeCache['stylus_' + filename] = data;
282 });
283 }
284 };
28549 generator.stylusFiles = resources.splice(start, rangeEnd - start + 1);
286133 generator.filename = 'stylus_' + _.map(generator.stylusFiles, function(file) { return file.originalSrc || file.src; }).join(';');
28749 generator.style = true;
28849 generator.stylus = true;
28949 generator.plugins = [];
290
29149 resources.splice(start, 0, generator);
29249 rangeEnd = undefined;
293 }
294
295 // Merge all consequtive stylus files together
296194 var resources = moduleContext.moduleResources,
297 len = resources.length,
298 rangeEnd;
299194 while (len--) {
300409 var resource = resources[len];
301
302409 if (/\.styl$/.test(resource.src)) {
30384 if (!rangeEnd) {
30449 rangeEnd = len;
305 }
306325 } else if (rangeEnd) {
3073 mergeResources(len + 1);
308 }
309 }
310194 if (rangeEnd != null) {
31146 mergeResources(0);
312 }
313194 complete();
314 });
315 }
316};
317

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/template.js

97%
76
74
2
LineHitsSource
1/**
2 * Template Plugin : Includes templates associated with a given file when said file is imported.
3 *
4 * Config:
5 * root:
6 * templates:
7 * Key value hash mapping file names to arrays of templates to include
8 *
9 * Special Values:
10 * auto-include: Key value pair mapping a regular expression key to a series of values
11 * to insert. Matching groups in the regular expression may be replaced using $i notation.
12 *
13 * Example: 'js/views/(.*)\\.js': ['templates/$1.handlebars']
14 *
15 * Mixins:
16 * The template plugin will mixin auto-include mappings per item, giving priority to the local version.
17 * File mappings will be mixed in but are executed within the scope of the mixin only. I.e. foo.js
18 * in the local file will not match file mappings for foo.js in a mixin.
19 *
20 */
211var _ = require('underscore'),
22 build = require('../build'),
23 path = require('path');
24
251module.exports = {
26 mode: 'scripts',
27 priority: 50,
28
29 loadMixin: function(context, next, complete) {
3063 var mixinTemplates = context.loadedLibrary.templates;
3163 if (mixinTemplates) {
328 var templates = context.libraries.originalConfig.templates || {},
33 configTemplates = _.clone(context.config.attributes.templates || templates),
34 assigned = false;
35
368 if (context.libraries.mergeHash('auto-include', templates, mixinTemplates, configTemplates)) {
373 assigned = true;
38 }
39
408 if (assigned) {
413 context.config.attributes.templates = configTemplates;
42 }
43 }
4463 next(complete);
45 },
46
47 resourceList: function(context, next, complete) {
48306 var library = context.resource.library,
49 attr = (library && library.parent) || context.config.attributes;
50
51306 next(function(err, ret) {
52306 if (err || !ret) {
530 return complete(err);
54 }
55
56306 function pushTemplates(templates) {
57271 _.each(templates, function(template) {
5840 var src = template.src;
5940 if (!src || (template.library && !template.library.attributes)) {
6030 var templateMixin = template.library ? context.libraries.getConfig(template.library) : library;
6130 src = context.libraries.resolvePath(template.src || template, templateMixin);
62 }
63
6440 ret.push({
65 src: src,
66 name: template.name || template.src || template,
67 library: template.library || library,
68 template: true
69 });
70 });
71 }
72
73306 var views = attr.templates || attr.views || {},
74 resource = context.resource.originalSrc || context.resource.src || context.resource,
75 mixinRoot = (context.resource.library && context.resource.library.root) || '';
76306 if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) {
77232 resource = resource.substring(mixinRoot.length);
78 }
79
80306 var deferComplete;
81306 if (build.filterResource(context.resource, context)) {
82262 pushTemplates(views[resource]);
83
84262 if (views['auto-include']) {
8510 var config = context.configCache['template-auto-include'];
8610 if (!config) {
875 config = module.exports.generateMappings(views['auto-include']);
885 context.configCache['template-auto-include'] = config;
89 }
90
9110 var autoIncludes = module.exports.autoIncludes(resource, config, context);
9210 if (autoIncludes.length) {
939 deferComplete = true;
94
959 context.fileUtil.fileList(autoIncludes, function(err, autoIncludes) {
969 if (err) {
970 return complete(err);
98 }
99
1009 var watchDirs = [];
1019 autoIncludes = _.filter(autoIncludes, function(file) {
10213 if (file.enoent) {
1033 watchDirs.push({watch: path.dirname(file.src)});
104 } else {
10510 return true;
106 }
107 });
108
1099 pushTemplates(autoIncludes);
1109 ret.push.apply(ret, watchDirs);
111
1129 complete(undefined, ret);
113 });
114 }
115 }
116 }
117306 if (!deferComplete) {
118297 complete(undefined, ret);
119 }
120 });
121 },
122
123 resource: function(context, next, complete) {
124220 var resource = context.resource;
125
126220 if (resource.watch) {
1272 function generator(buildContext, callback) {
128 // Ensure that the directory actually exists
1292 var path = context.fileUtil.resolvePath(resource.watch);
1302 context.fileUtil.stat(path, function(err, stat) {
131 // Ignore any errors here
1322 var inputs = [];
1332 if (stat && stat.isDirectory()) {
1342 inputs.push(path);
135 }
1362 callback(undefined, {inputs: inputs, data: '', noSeparator: true});
137 });
138 }
1392 complete(undefined, generator);
140 } else {
141218 next(complete);
142 }
143 },
144
145 autoIncludes: function(resource, config, context) {
14610 var autoIncludes = [];
14710 _.each(config, function(mapping) {
14810 var remap = module.exports.remapFile(mapping, resource, context);
14910 if (remap) {
1509 autoIncludes.push.apply(autoIncludes, remap);
151 }
152 });
15310 return autoIncludes;
154 },
155 generateMappings: function(autoInclude) {
1565 return _.map(autoInclude, function(templates, source) {
1575 if (!_.isArray(templates)) {
1582 templates = [templates];
159 }
1605 return {regex: new RegExp(source), templates: templates};
161 });
162 },
163 remapFile: function(mapping, resource, context) {
164 /*jshint boss:true */
16513 var match;
16613 if (match = mapping.regex.exec(resource)) {
16711 return _.map(mapping.templates, function(template) {
168 // Work in reverse so $10 takes priority over $1
16921 var i = match.length;
17021 while (i--) {
17146 template = template.replace('$' + i, match[i]);
172 }
17321 return {name: template, src: context.libraries.resolvePath(template, template.library || context.resource.library)};
174 });
175 }
176 }
177};
178

/Users/kpdecker/dev/walmart/lumbar/lib/plugins/update-externals.js

92%
57
53
4
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 cheerio = require('cheerio'),
4 path = require('path'),
5 basename = path.basename,
6 dirname = path.dirname,
7 pathRelative = require('../path-relative'); // Shim for 0.4 support
8
91module.exports = {
10 mode: 'static',
11 priority: 50,
12
13 updateHtmlReferences: function(context, content, callback) {
1415 function updateResources(mode, query, create) {
1530 return function(callback) {
1629 async.forEach($(query), function(el, callback) {
1710 el = $(el);
1810 var module = (el.attr('src') || el.attr('href')).replace(/^module:/, '');
1910 context.fileNamesForModule(mode, module, function(err, fileNames) {
2010 if (err) {
212 return callback(err);
22 }
23
24 // Generate replacement elements for each of the entries
258 var content = fileNames.map(function(fileName) {
268 if (fileName.server) {
270 return '';
28 }
298 return create(loadDirName + basename(fileName.fileName.path) + '.' + fileName.fileName.extension);
30 });
31
32 // Output and kill the original
338 el.replaceWith(content.join(''));
34
358 callback();
36 });
37 },
38 callback);
39 };
40 }
4115 var $ = cheerio.load(content),
42 loadDirName = '';
4315 async.series([
44 function(callback) {
45 // Output the load prefix script we we have a module: script reference
4615 var firstScript = $('script[src^="module:"]');
4715 if (firstScript) {
4815 context.plugins.get('module-map').buildMap(context, function(err, map, loadPrefix) {
4915 if (err) {
500 return callback(err);
51 }
52
5315 var noFileComponent = !loadPrefix;
5415 loadPrefix = context.platformPath + loadPrefix;
5515 var dirname = path.dirname(loadPrefix + 'a'); // Force a component for the trailing '/' case
56
57 // Only remap load prefix if not defined by the user
5815 if (!(loadDirName = context.config.loadPrefix())) {
5914 var resourcePath = path.dirname(context.fileName.substring(context.outdir.length + 1));
6014 loadPrefix = pathRelative.relative(resourcePath, loadPrefix);
6114 loadDirName = pathRelative.relative(resourcePath, dirname);
62
6314 if (loadDirName) {
647 loadDirName += '/';
65 }
6614 if (loadPrefix && noFileComponent) {
677 loadPrefix += '/';
68 }
69 } else {
70 // A load prefix was given, just combine this with the module map prefix
711 loadPrefix = loadDirName + loadPrefix;
721 if (dirname !== '.') {
731 loadDirName += dirname + '/';
74 }
75 }
76
7715 var script = '<script type="text/javascript">var lumbarLoadPrefix = \'' + loadPrefix + '\';</script>';
7815 firstScript.before(script);
7915 callback();
80 });
81 } else {
820 callback();
83 }
84 },
85 updateResources('scripts', 'script[src^="module:"]', function(href) {
867 return '<script type="text/javascript" src="' + href + '"></script>';
87 }),
88 updateResources('styles', 'link[href^="module:"]', function(href) {
891 return '<link rel="stylesheet" type="text/css" href="' + href + '"/>';
90 })
91 ],
92 function(err) {
9315 callback(err, $.html());
94 });
95 },
96
97 resource: function(context, next, complete) {
9828 var resource = context.resource;
9928 if (resource['update-externals'] || (/\.html?$/.test(resource.src) && resource['update-externals'] !== false)) {
1006 next(function(err, resource) {
1016 function generator(context, callback) {
102 // Load the source data
1036 context.loadResource(resource, function(err, file) {
1046 if (err) {
1050 return complete(err);
106 }
107
108 // Update the content
1096 module.exports.updateHtmlReferences(context, file.content, function(err, data) {
1106 callback(err, {
111 data: data,
112 inputs: file.inputs
113 });
114 });
115 });
116 }
117
118 // Include any attributes that may have been defined on the base entry
1196 if (!_.isString(resource)) {
1206 _.extend(generator, resource);
121 }
1226 complete(undefined, generator);
123 });
124 } else {
12522 next(complete);
126 }
127 }
128};
129

/Users/kpdecker/dev/walmart/lumbar/lib/templateUtil.js

100%
4
4
0
LineHitsSource
11const ESCAPER_LUT = {
2 '\b': '\\b',
3 '\f': '\\f',
4 '\n': '\\n',
5 '\r': '\\r',
6 '\t': '\\t',
7 '\v': '\\v',
8 '\'': '\\\'',
9 '\"': '\\\"',
10 '\\': '\\\\'
11};
121const ESCAPER = /[\b\f\n\r\t\v\'\"\\]/g;
13
141exports.escapeJsString = function(string) {
15 // TODO : Handle unicode escapes
1662 return string.replace(ESCAPER, function(c) { return ESCAPER_LUT[c] || c; });
17};
18

/Users/kpdecker/dev/walmart/lumbar/lib/util/file-map.js

47%
71
34
37
LineHitsSource
11var _ = require('underscore'),
2 async = require('async'),
3 fu = require('../fileUtil'),
4 path = require('path'),
5 dirname = path.dirname,
6 basename = path.basename,
7 sourceMap,
8 SourceMapConsumer,
9 SourceMapGenerator;
10
111try {
121 sourceMap = require('source-map');
131 SourceMapConsumer = sourceMap.SourceMapConsumer;
141 SourceMapGenerator = sourceMap.SourceMapGenerator;
15} catch (err) {
16 /* NOP */
17}
18
191const WARNING_CONTEXT = 3,
20 GENERATED = '<generated>';
21
221module.exports = exports = function(output) {
23106 this.output = output;
24106 if (SourceMapGenerator) {
25106 this.generator = new SourceMapGenerator({file: output});
26 }
27
28106 this.contentCache = {};
29106 this.line = 1;
30106 this.column = 1;
31106 this._content = '';
32};
33
341exports.prototype.add = function(name, content, context) {
35483 this._sourceMap = '';
36483 this._consumer = undefined;
37
38483 var lines = content.split('\n');
39483 if (name) {
40178 this.contentCache[name] = {
41 lines: lines,
42 context: context
43 };
44 }
45
46483 if (this.generator) {
47483 _.each(lines, function(line, index) {
482750 this.generator.addMapping({
49 source: name || GENERATED,
50 generated: {
51 line: this.line + index,
52 column: index ? 1 : this.column
53 },
54 original: {
55 line: index + 1,
56 column: 1
57 }
58 });
59 }, this);
60 }
61
62483 this.line += lines.length - 1;
63483 if (lines.length >= 2) {
64461 this.column = 1;
65 }
66483 this.column += lines[lines.length - 1].length;
67
68483 this._content += content;
69};
701exports.prototype.content = function() {
71271 return this._content;
72};
731exports.prototype.sourceMap = function() {
740 this._sourceMap = this._sourceMap || this.generator.toString();
750 return this._sourceMap;
76};
77
781exports.prototype.sourceMapToken = function() {
790 return '//@ sourceMappingURL=' + basename(this.output) + '.map\n';
80};
81
821exports.prototype.writeSourceMap = function(options) {
830 var tasks = [],
84 outputDir = dirname(this.output) + '/',
85 self = this;
86
870 tasks.push(function(callback) {
880 fu.writeFile((options.mapDestination || self.output) + '.map', self.sourceMap(), callback);
89 });
900 if (options.outputSource) {
910 _.each(this.contentCache, function(content, name) {
920 tasks.push(function(callback) {
930 var file = outputDir + name;
940 fu.ensureDirs(dirname(file), function(err) {
950 if (err) {
960 return callback(err);
97 }
980 fu.writeFile(file, content.lines.join('\n'), callback);
99 });
100 });
101 });
102 }
103
1040 async.parallel(tasks, function(err) {
1050 if (err) {
1060 throw err;
107 }
108
1090 self.add(undefined, self.sourceMapToken());
1100 options.callback();
111 });
112};
113
1141exports.prototype.context = function(line, column) {
1150 if (!SourceMapConsumer) {
1160 return {
117 file: this.output,
118 line: line,
119 column: column
120 };
121 }
122
1230 this._consumer = this._consumer || new SourceMapConsumer(this.sourceMap());
1240 var original = this._consumer.originalPositionFor({line: line, column: column}),
125 lines;
126
1270 var content = this.contentCache[original.source];
1280 if (content) {
1290 var lines = content.lines,
130 line = original.line - 1,
131 start = Math.max(line - WARNING_CONTEXT + 1, 0),
132 end = Math.min(line + WARNING_CONTEXT, lines.length),
133 gutterWidth = (end + '').length;
1340 line = line + 1;
135
1360 lines = lines.slice(start, end).map(function(value, index) {
1370 var lineNum = start + index + 1,
138 lineText = lineNum + '',
139 buffer = '';
1400 for (var i = lineText.length; i < gutterWidth; i++) {
1410 buffer += ' ';
142 }
1430 buffer += lineText;
1440 buffer += (lineNum === line) ? ': ' : ' ';
1450 buffer += value;
1460 return buffer;
147 });
148 } else {
1490 return;
150 }
151
1520 return {
153 file: original.source,
154 fileContext: content.context,
155 line: original.line,
156 column: original.column,
157 context: lines
158 };
159};
160

/Users/kpdecker/dev/walmart/lumbar/lib/watch-manager.js

92%
42
39
3
LineHitsSource
11var _ = require('underscore'),
2 EventEmitter = require('events').EventEmitter,
3 fu = require('./fileUtil'),
4 path = require('path'),
5 watcher = require('./watcher');
6
71function WatchManager() {
827 EventEmitter.call(this);
9
1027 this.reset();
11
1227 this._exec = this.setupExec();
13}
14
151WatchManager.prototype = {
16 configFile: function(path, mixins, callback) {
1716 if (_.isFunction(mixins)) {
180 callback = mixins;
190 mixins = undefined;
20 }
21
2216 var self = this;
2316 watcher.watchFile(path, mixins || [], function() {
244 self.emit('watch-change', {fileName: path, config: true});
25
264 self.pushChange({callback: callback, fileName: path, config: true});
27 });
28 },
29 moduleOutput: function(status, callback) {
3072 var self = this;
31
3272 function theWatcher(type, filename, sourceChange) {
3336 self.emit('watch-change', {fileName: sourceChange, output: status.fileName});
3436 self.pushChange({
35 callback: callback,
36 type: type,
37 fileName: status.fileName,
38 sourceChange: sourceChange
39 });
40 }
41
42336 var input = status.inputs.map(function(input) { return fu.resolvePath(input.dir || input); }),
43 removed = _.difference(this.watching[status.fileName], input);
44
4572 if (removed.length) {
460 watcher.unwatch(status.fileName, removed);
47 }
48
4972 watcher.watchFile({ virtual: status.fileName }, input, theWatcher);
5072 this.watching[status.fileName] = input;
51 },
52
53
54 setupExec: function() {
5511 return _.debounce(_.bind(this.flushQueue, this), 500);
56 },
57 flushQueue: function() {
5821 if (this.queue.length) {
5921 _.each(this.queue, function(change) {
6038 change.callback();
61 });
6221 this.queue = [];
63 }
64 },
65
66 reset: function() {
67 // Cleanup what we can, breaking things along the way
68 // WARN: This prevents concurrent execution within the same process.
6948 watcher.unwatchAll();
70
7148 this.watching = {};
7248 this.queue = [];
73 },
74 pushChange: function(change) {
7557 fu.resetCache(change.sourceChange);
7657 if (change.type === 'remove' && change.sourceChange) {
773 fu.resetCache(path.dirname(change.sourceChange));
78 }
79
8057 if (_.find(this.queue, function(existing) {
8144 return existing.config || (change.fileName && (existing.fileName === change.fileName));
82 })) {
83 // If we have a pending config change or changes to the same file that has not started then
84 // we can ignore subsequent changes
857 return;
86 }
87
8850 if (change.config) {
8910 this.reset();
90 }
91
9250 this.queue.push(change);
9350 this._exec();
94 }
95};
96
971WatchManager.prototype.__proto__ = EventEmitter.prototype;
98
991exports = module.exports = WatchManager;
100

/Users/kpdecker/dev/walmart/lumbar/lib/watcher.js

90%
101
91
10
LineHitsSource
1/**
2 * Adds dependency watching to the core fs.watchFile implementation.
3 */
41var _ = require('underscore'),
5 fs = require('fs');
6
71var watchedFiles = {};
8
91function notifyWatch(filename, type, sourceChange, trigger) {
1075 var watchInfo = watchedFiles[filename];
1175 if (watchInfo) {
1272 var inQueue = _.find(watchInfo.queue, function(entry) {
130 return entry.type === type
14 && entry.filename === filename
15 && entry.sourceChange === sourceChange;
16 });
17
1872 if (!inQueue) {
1972 var entry = {type: type, filename: filename, sourceChange: sourceChange};
2072 watchInfo.queue.push(entry);
21
2272 function exec() {
2372 watchInfo.queue = _.without(watchInfo.queue, entry);
24
2572 if (watchInfo.callback) {
2649 watchInfo.callback(type, filename, sourceChange);
27 }
2872 watchInfo.parents.forEach(function(parent) {
2940 notifyWatch(parent, type, sourceChange, trigger);
30 });
31 }
32
3372 if (trigger) {
3466 exec();
35 } else {
36 // Debounce so we don't output multiple instances of the same event on platforms
37 // such as linux that may send multiple events on write, etc.
386 _.defer(exec, 200);
39 }
40 }
41 }
42}
43
441function watchFile(filename, callback, parent) {
45176 var watchInfo = {
46 callback: callback,
47 parents: [],
48 queue: []
49 };
50176 if (parent) {
51101 watchInfo.parents.push(parent);
52 }
53176 watchedFiles[filename.virtual || filename] = watchInfo;
54
55176 if (!filename.virtual) {
566 var hasRetried;
57
586 (function watch(ignoreError) {
597 try {
607 var oldStat = fs.statSync(filename),
61 lastType,
62 rewatch;
63
647 var changeHandler = _.debounce(function() {
653 if (rewatch) {
66 // Attempt to reattach on rename
671 watchInfo.watch.close();
681 watch(true);
69 }
703 notifyWatch(filename, lastType, filename);
71 }, 1000);
72
737 watchInfo.watch = fs.watch(filename, function(type) {
746 try {
756 var newStat = fs.statSync(filename);
764 if (newStat.isDirectory()) {
771 notifyWatch(filename, 'create', filename);
783 } else if (newStat.size !== oldStat.size || newStat.mtime.getTime() > oldStat.mtime.getTime()) {
793 oldStat = newStat;
803 if (type === 'rename') {
811 rewatch = true;
82 }
833 lastType = type;
843 changeHandler();
85 }
86 } catch (err) {
872 if (err.code === 'ENOENT') {
88 // The file was removed by the time we got to it. This could be a case of it actually being removed
89 // or a race condtion with rewriting APIs.
902 watchInfo.watch.close();
91
92 // Pause a bit to see if this was a replace that we raced with...
932 setTimeout(function() {
942 try {
952 fs.statSync(filename); // No exception: file still exists, notify and restart the watch
960 notifyWatch(filename, 'change', filename);
970 watch(true);
98 } catch (err) {
99 // The file is really gone... or we just got hit with a race condtion twice. Give up.
1002 notifyWatch(filename, 'remove', filename);
101 }
102 }, 500);
103 } else {
1040 throw err;
105 }
106 }
107 });
108 } catch (err) {
1090 if (!hasRetried && err.code === 'EMFILE') {
1100 hasRetried = true;
1110 setTimeout(function() {
1120 watch(ignoreError);
113 }, 250);
1140 } else if (!ignoreError) {
1150 throw err;
116 }
117 }
118 })();
119 }
120}
121
1221exports.watchFile = function(filename, dependencies, callback) {
12395 var watch = watchedFiles[filename.virtual || filename];
12495 if (!watch) {
125 // Create a watch on this and all others
12675 watchFile(filename, callback);
127 } else {
12820 watch.callback = callback;
129 }
130
13195 filename = filename.virtual || filename;
132
13395 dependencies.forEach(function(depend) {
134270 var watch = watchedFiles[depend.virtual || depend];
135270 if (!watch) {
136101 watchFile(depend, undefined, filename);
137 } else {
138169 if (!_.contains(watch.parents, filename)) {
13990 watch.parents.push(filename);
140 }
141 }
142 });
143};
144
1451exports.trigger = function(type, filename) {
14629 notifyWatch(filename, type, filename, true);
147};
148
1491exports.unwatch = function(filename, dependencies) {
1505 var watch = watchedFiles[filename.virtual || filename];
1515 if (!watch) {
1521 return;
153 }
154
155 // Remove the callback
1564 if (!dependencies) {
1572 watch.callback = undefined;
158 }
159
160 // For each dependency remove the parent link
1614 filename = filename.virtual || filename;
162
1634 _.each(dependencies || watchedFiles, function(depend) {
1646 var watch = watchedFiles[depend.virtual || depend];
1656 if (!watch) {
1664 return;
167 }
168
1692 watch.parents = _.without(watch.parents, filename);
170 });
171
172 // Kill this watch if it can't trigger or fire
1734 var canTrigger = watch.watch || _.some(watchedFiles, function(watch) {
1749 return _.contains(watch.parents, filename);
175 });
1764 if (!watch.callback || !canTrigger) {
1773 unwatch(filename);
178 }
179
180 // Kill any other watches that might not be valid anymore
1814 _.each(_.clone(watchedFiles), function(watch, name) {
1826 if (!watch.callback && !watch.parents.length) {
1832 exports.unwatch(name);
184 }
185 });
186};
1871exports.unwatchAll = function() {
18856 _.each(watchedFiles, function(watch, name) {
189173 unwatch(name);
190 });
191};
192
1931function unwatch(name) {
194176 watchedFiles[name].callback = undefined;
195176 if (watchedFiles[name].watch) {
1966 watchedFiles[name].watch.close();
197 }
198176 delete watchedFiles[name];
199}
200
Done, without errors.