1 | var asp = require('bluebird').promisify;
|
2 | var Promise = require('bluebird');
|
3 | var glob = require('glob');
|
4 | var path = require('path');
|
5 | var url = require('url');
|
6 |
|
7 | var getLoadDependencies = require('./trace').getLoadDependencies;
|
8 |
|
9 | var fromFileURL = require('./utils').fromFileURL;
|
10 | var toFileURL = require('./utils').toFileURL;
|
11 |
|
12 | var verifyTree = require('./utils').verifyTree;
|
13 |
|
14 | function parseExpression(expressionString) {
|
15 | expressionString = ' + ' + expressionString;
|
16 |
|
17 | var index = 0;
|
18 | var operations = [];
|
19 | var operatorRegex = /[\+\-\&]/;
|
20 | var errorMessagesFromIndex = 3;
|
21 |
|
22 | function getNextIdentifier() {
|
23 | eatWhitespace();
|
24 | var firstChar = expressionString.charAt(index);
|
25 |
|
26 | if (operatorRegex.test(firstChar)){
|
27 | throw 'Syntax Error: Identifier or sub expression expected after <' + expressionString.slice(errorMessagesFromIndex).substr(0, index - errorMessagesFromIndex) + '> but found <' + firstChar + '> instead';
|
28 | }
|
29 |
|
30 | if (firstChar === '(') {
|
31 | var closingParenIndex = index,
|
32 | numOpenBeforeSelf = 0;
|
33 |
|
34 | while (++closingParenIndex < expressionString.length){
|
35 | if (expressionString.charAt(closingParenIndex) === '('){
|
36 | numOpenBeforeSelf++;
|
37 | } else if (expressionString.charAt(closingParenIndex) === ')') {
|
38 | if (numOpenBeforeSelf){
|
39 | numOpenBeforeSelf--;
|
40 | } else {
|
41 | break;
|
42 | }
|
43 | }
|
44 | }
|
45 | if (expressionString.charAt(closingParenIndex) !== ')'){
|
46 | throw 'Syntax Error: Expression <' + expressionString.substr(index) + '> is never terminated. Did you forget to add a closing ")"?';
|
47 | }
|
48 |
|
49 | var wholeExpression = expressionString.substring(index + 1, closingParenIndex);
|
50 | index = closingParenIndex + 1;
|
51 | return { bulkOperation: wholeExpression };
|
52 | }
|
53 |
|
54 | var result = "";
|
55 |
|
56 | for (; index < expressionString.length; index++) {
|
57 | var currentChar = expressionString.charAt(index);
|
58 |
|
59 | if (/^\s+[\+\-\&]\s+/.test(expressionString.substr(index))) {
|
60 | return result;
|
61 | } else {
|
62 | result += currentChar;
|
63 | }
|
64 | }
|
65 | return result.replace(/\s+$/, '');
|
66 | }
|
67 |
|
68 | function getNextOperator() {
|
69 | eatWhitespace();
|
70 | if (index === expressionString.length) return null;
|
71 |
|
72 | var candidateResult = expressionString.charAt(index++);
|
73 |
|
74 | if (!operatorRegex.test(candidateResult)){
|
75 | throw 'Syntax Error: An operator was expected after <' + expressionString.slice(errorMessagesFromIndex).substr(0, index - 1 - errorMessagesFromIndex) + '> but found <' + expressionString.substring(index - 1) + '> instead';
|
76 | }
|
77 |
|
78 | return candidateResult;
|
79 | }
|
80 |
|
81 | function eatWhitespace() {
|
82 |
|
83 | for (; index < expressionString.length; index++) {
|
84 | if (/\S/.test(expressionString.charAt(index))) {
|
85 | break;
|
86 | }
|
87 | }
|
88 | }
|
89 |
|
90 | var operator;
|
91 | while (index < expressionString.length && (operator = getNextOperator())) {
|
92 | var moduleNameOrSubExpression = getNextIdentifier();
|
93 |
|
94 | if (typeof moduleNameOrSubExpression === 'object'){
|
95 | operations.push({
|
96 | operator: operator,
|
97 | bulkOperation: moduleNameOrSubExpression.bulkOperation
|
98 | });
|
99 | } else {
|
100 |
|
101 | var singleModule = moduleNameOrSubExpression.substr(0, 1) == '[' && moduleNameOrSubExpression.substr(moduleNameOrSubExpression.length - 1, 1) == ']';
|
102 | if (singleModule) {
|
103 | moduleNameOrSubExpression = moduleNameOrSubExpression.substr(1, moduleNameOrSubExpression.length - 2);
|
104 | }
|
105 |
|
106 | var canonicalized = moduleNameOrSubExpression.substr(0, 1) == '`' && moduleNameOrSubExpression.substr(moduleNameOrSubExpression.length - 1, 1) == '`';
|
107 | if (canonicalized) {
|
108 | moduleNameOrSubExpression = moduleNameOrSubExpression.substr(1, moduleNameOrSubExpression.length - 2);
|
109 | }
|
110 |
|
111 | operations.push({
|
112 | operator: operator,
|
113 | moduleName: moduleNameOrSubExpression,
|
114 | singleModule: singleModule,
|
115 | canonicalized: canonicalized
|
116 | });
|
117 | }
|
118 | }
|
119 |
|
120 | return operations;
|
121 | }
|
122 |
|
123 | function getTreeOperation(symbol) {
|
124 | if (symbol == '+')
|
125 | return addTrees;
|
126 | else if (symbol == '-')
|
127 | return subtractTrees;
|
128 | else if (symbol == '&')
|
129 | return intersectTrees;
|
130 | else
|
131 | throw new TypeError('Unknown operator ' + symbol);
|
132 | }
|
133 |
|
134 | function getTreeModuleOperation(builder, symbol, traceOpts) {
|
135 | if (symbol == '+')
|
136 | return function(tree, canonical) {
|
137 | var addedTree = {};
|
138 | for (var p in tree)
|
139 | addedTree[p] = tree[p];
|
140 |
|
141 | return builder.tracer.getLoadRecord(canonical, traceOpts).then(function(load) {
|
142 | addedTree[canonical] = load;
|
143 | return addedTree;
|
144 | });
|
145 | };
|
146 | else if (symbol == '-')
|
147 | return function(tree, canonical) {
|
148 | var subtractedTree = {};
|
149 | for (var p in tree) {
|
150 | if (p != canonical)
|
151 | subtractedTree[p] = tree[p];
|
152 | }
|
153 | return subtractedTree;
|
154 | };
|
155 | else if (symbol == '&')
|
156 | throw new TypeError('Single modules cannot be intersected.');
|
157 | else
|
158 | throw new TypeError('Unknown operator ' + symbol);
|
159 | }
|
160 |
|
161 | function expandGlobAndCanonicalize(builder, operation) {
|
162 | var loader = builder.loader;
|
163 |
|
164 |
|
165 | if (operation.moduleName.indexOf('*') == -1) {
|
166 | if (operation.canonicalized)
|
167 | return [operation];
|
168 |
|
169 | return loader.normalize(operation.moduleName)
|
170 | .then(function(normalized) {
|
171 | operation.moduleName = builder.getCanonicalName(normalized);
|
172 | return [operation];
|
173 | });
|
174 | }
|
175 |
|
176 |
|
177 | var metadata = {};
|
178 | var globSuffix = operation.moduleName[operation.moduleName.length - 1] == '*';
|
179 | var pluginSyntax = operation.moduleName.indexOf('!') != -1;
|
180 |
|
181 | return Promise.resolve()
|
182 | .then(function() {
|
183 | if (operation.canonicalized) {
|
184 | return loader.decanonicalize(operation.moduleName);
|
185 | }
|
186 | else {
|
187 |
|
188 | return loader.normalizeSync(operation.moduleName);
|
189 | }
|
190 | })
|
191 | .then(function(normalized) {
|
192 |
|
193 | if (globSuffix && !operation.canonicalized) {
|
194 | var extIndex = normalized.lastIndexOf('.');
|
195 | if (extIndex != -1 && normalized[extIndex - 1] == '*')
|
196 | normalized = normalized.substr(0, extIndex);
|
197 | }
|
198 |
|
199 | return loader.locate({ name: normalized, metadata: metadata });
|
200 | })
|
201 | .then(function(address) {
|
202 |
|
203 | return asp(glob)(fromFileURL(address), {
|
204 | nobrace: true,
|
205 | noext: true,
|
206 | nodir: true
|
207 | });
|
208 | })
|
209 | .then(function(addresses) {
|
210 | return (metadata.loader && pluginSyntax ? loader.normalize(metadata.loader) : Promise.resolve())
|
211 | .then(function(loaderSyntaxName) {
|
212 | return addresses.map(function(file) {
|
213 | return {
|
214 | operator: operation.operator,
|
215 | moduleName: builder.getCanonicalName(toFileURL(file) + (loaderSyntaxName ? '!' + loader.getCanonicalName(loaderSyntaxName) : '')),
|
216 | singleModule: operation.singleModule
|
217 | };
|
218 | });
|
219 | });
|
220 | });
|
221 | }
|
222 |
|
223 | exports.traceExpression = function(builder, expression, traceOpts) {
|
224 | if (!expression)
|
225 | throw new Error('A module expression must be provided to trace.');
|
226 |
|
227 | if (expression instanceof Array) {
|
228 | var tree = {};
|
229 | return Promise.all(expression.map(function(moduleName) {
|
230 | return builder.loader.normalize(moduleName)
|
231 | .then(function(normalized) {
|
232 | var canonical = builder.getCanonicalName(normalized);
|
233 | return builder.tracer.getLoadRecord(canonical, traceOpts)
|
234 | .then(function(load) {
|
235 | tree[canonical] = load;
|
236 | });
|
237 | });
|
238 | }))
|
239 | .then(function() {
|
240 | return tree;
|
241 | });
|
242 | }
|
243 |
|
244 | return Promise
|
245 | .resolve(expandAndCanonicalizeExpression(builder, expression))
|
246 | .then(function processExpandedOperations(expandedOperations) {
|
247 |
|
248 | return expandedOperations.reduce(function(p, op) {
|
249 | return p.then(function(curTree) {
|
250 |
|
251 | if (op.singleModule)
|
252 | return getTreeModuleOperation(builder, op.operator, traceOpts)(curTree, op.moduleName);
|
253 |
|
254 | if (op.operationsTree){
|
255 | return processExpandedOperations(op.operationsTree).then(function(expandedTree){
|
256 | return getTreeOperation(op.operator)(curTree, expandedTree);
|
257 | });
|
258 | }
|
259 |
|
260 | return builder.tracer.traceCanonical(op.moduleName, traceOpts)
|
261 | .then(function(nextTrace) {
|
262 | return getTreeOperation(op.operator)(curTree, nextTrace.tree);
|
263 | });
|
264 | });
|
265 | }, Promise.resolve({}));
|
266 | });
|
267 | };
|
268 |
|
269 | function expandAndCanonicalizeExpression(builder, expression) {
|
270 | var operations = parseExpression(expression);
|
271 | var expandPromise = Promise.resolve(1);
|
272 | var expandedOperations = [];
|
273 |
|
274 | operations.forEach(function(operation){
|
275 | if (operation.bulkOperation) {
|
276 | var expandedTreePromise = expandAndCanonicalizeExpression(builder, operation.bulkOperation);
|
277 | expandPromise = expandPromise.then(function() {
|
278 | return Promise.resolve(expandedTreePromise)
|
279 | .then(function(expressionsOperations){
|
280 | expandedOperations = expandedOperations.concat({ operator: operation.operator, operationsTree: expressionsOperations });
|
281 | });
|
282 | });
|
283 | } else {
|
284 | expandPromise = expandPromise.then(function() {
|
285 | return Promise.resolve(expandGlobAndCanonicalize(builder, operation))
|
286 | .then(function (expanded) {
|
287 | expandedOperations = expandedOperations.concat(expanded);
|
288 | });
|
289 | })
|
290 | }
|
291 | });
|
292 |
|
293 | return Promise.resolve(expandPromise).then(function(){ return expandedOperations; });
|
294 | }
|
295 |
|
296 |
|
297 | exports.intersectTrees = intersectTrees;
|
298 | function intersectTrees(tree1, tree2) {
|
299 | verifyTree(tree1);
|
300 | verifyTree(tree2);
|
301 |
|
302 | var name;
|
303 | var intersectTree = {};
|
304 |
|
305 | var tree1Names = [];
|
306 | for (name in tree1)
|
307 | tree1Names.push(name);
|
308 |
|
309 | for (name in tree2) {
|
310 | if (tree1Names.indexOf(name) == -1)
|
311 | continue;
|
312 |
|
313 | if (tree1[name] === false && tree2[name] === false)
|
314 | continue;
|
315 |
|
316 | intersectTree[name] = tree1[name] || tree2[name];
|
317 | }
|
318 |
|
319 | return intersectTree;
|
320 | }
|
321 |
|
322 |
|
323 | exports.addTrees = addTrees;
|
324 | function addTrees(tree1, tree2) {
|
325 | verifyTree(tree1);
|
326 | verifyTree(tree2);
|
327 |
|
328 | var name;
|
329 | var unionTree = {};
|
330 |
|
331 | for (name in tree2)
|
332 | unionTree[name] = tree2[name];
|
333 |
|
334 | for (name in tree1)
|
335 | if (!(name in unionTree))
|
336 | unionTree[name] = tree1[name];
|
337 |
|
338 | return unionTree;
|
339 | }
|
340 |
|
341 |
|
342 | exports.subtractTrees = subtractTrees;
|
343 | function subtractTrees(tree1, tree2) {
|
344 | verifyTree(tree1);
|
345 | verifyTree(tree2);
|
346 |
|
347 | var name;
|
348 | var subtractTree = {};
|
349 |
|
350 | for (name in tree1)
|
351 | subtractTree[name] = tree1[name];
|
352 |
|
353 | for (name in tree2) {
|
354 | if (tree2[name] !== false)
|
355 | delete subtractTree[name];
|
356 | }
|
357 |
|
358 | return subtractTree;
|
359 | }
|
360 |
|
361 |
|
362 | exports.traverseTree = traverseTree;
|
363 | function traverseTree(tree, moduleName, visitor, traceOpts, reversePost, parent, seen) {
|
364 | if (!seen) {
|
365 |
|
366 | traceOpts = traceOpts || {};
|
367 | verifyTree(tree);
|
368 | }
|
369 |
|
370 | seen = seen || [];
|
371 | seen.push(moduleName);
|
372 | parent = parent || null;
|
373 |
|
374 | var curNode = tree[moduleName];
|
375 |
|
376 | if (curNode && visitor(moduleName, parent) !== false) {
|
377 | var deps = getLoadDependencies(curNode, traceOpts, traceOpts.conditions, traceOpts.inlineConditions);
|
378 | if (reversePost)
|
379 | deps = deps.reverse();
|
380 | deps.forEach(function(dep) {
|
381 | if (seen.indexOf(dep) == -1)
|
382 | traverseTree(tree, dep, visitor, traceOpts, reversePost, moduleName, seen);
|
383 | });
|
384 | }
|
385 | } |
\ | No newline at end of file |