Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / build / build-manifest.js
1 // Creates list of class with its info ("metaclass") in JSON format.
2 // Requirement: hammerjs (see https://github.com/senchalabs/hammerjs).
3
4 /*global system:true, fs:true, Reflect:true */
5
6 if (system.args.length !== 3) {
7     system.print('Usage:');
8     system.print('  hammerjs create-manifest.js /path/to/src/ output.json');
9     system.exit(-1);
10 }
11
12 // Traverses the specified path and collects all *.js files.
13 // Note: the traversal is recursive to all subdirectories.
14
15 var scanDirectory = function (path) {
16     var entries = [],
17         subdirs;
18     if (fs.exists(path) && fs.isFile(path) && path.match('.js$')) {
19         entries.push(path);
20     } else if (fs.isDirectory(path)) {
21         fs.list(path).forEach(function (e) {
22             subdirs = scanDirectory(path + '/' + e);
23             subdirs.forEach(function (s) {
24                 entries.push(s);
25             });
26         });
27     }
28     return entries;
29 };
30
31 var scanDirectories = function (paths) {
32     var sources = [],
33         excludes =[];
34     paths.split(',').forEach(function (path) {
35         if (path[0] === '-') {
36             excludes.push(path.substring(1));
37         } else {
38             scanDirectory(path).forEach(function (source) {
39                 sources.push(source);
40             });
41         }
42     });
43     sources = sources.filter(function (e) {
44         var included = true;
45         excludes.forEach(function (f) {
46             if (e.substring(0, f.length) === f) {
47                 included = false;
48             }
49         });
50         return included;
51     });
52     return sources;
53 };
54
55 var timestamp = Date.now(),
56     manifest = [],
57     sources = scanDirectories(system.args[1]);
58
59 system.print('Analyzing ' + sources.length + ' source files. Please wait...');
60
61 sources.forEach(function (fileName) {
62
63     // Loads the content of a file and returns the syntax tree.
64     var parse = function (fname)
65     {
66         var f = fs.open(fname, 'r'),
67             content = '', line;
68         while (true) {
69             line = f.readLine();
70             if (line.length === 0) {
71                 break;
72             }
73             content += line;
74         }
75         f.close();
76         return Reflect.parse(content);
77     };
78
79     // Recursively visits v and all child objects of v and executes
80     // functions f for each visit.
81     var visit = function (v, f) {
82         var child;
83         f(v);
84         for (var i in v) {
85             child = v[i];
86             if (child !== null && typeof child === 'object') {
87                 visit(child, f);
88             }
89         }
90     };
91
92     // Matches the subtree 'code' with Ext.extend(Ext.foo, Ext.bar, ...)
93     // or Ext.foo = Ext.extend(Ext.bar, ...).
94     // Returns the metaclass if successful, otherwise returns undefined.
95     var matchExtend = function (code) {
96         var meta = {},
97             properties;
98         if ((code.type === 'ExpressionStatement') &&
99             (typeof code.expression !== 'undefined') &&
100             (code.expression.type === 'CallExpression') &&
101             (typeof code.expression.callee !== 'undefined') &&
102             (code.expression.callee.type === 'MemberExpression') &&
103             (code.expression.arguments.length === 3) &&
104             (code.expression.callee.object.type === 'Identifier') &&
105             (code.expression.callee.object.name === 'Ext') &&
106             (code.expression.callee.property.type === 'Identifier') &&
107             (code.expression.callee.property.name === 'extend')) {
108             meta.className = '';
109             meta.extend = '';
110             visit(code.expression.arguments[0], function (v) {
111                 if (v.type === 'Identifier') {
112                     if (meta.className.length > 0)
113                         meta.className += '.';
114                     meta.className += v.name;
115                 }
116             });
117             visit(code.expression.arguments[1], function (v) {
118                 if (v.type === 'Identifier') {
119                     if (meta.extend.length > 0)
120                         meta.extend += '.';
121                     meta.extend += v.name;
122                 }
123             });
124             properties = code.expression.arguments[2].properties;
125             if (properties && properties.length > 0) {
126                 properties.forEach(function (e) {
127                     if ((e.value.type === 'FunctionExpression')) {
128                         if (!meta.functions) {
129                             meta.functions = [];
130                         }
131                         meta.functions.push(e.key.name);
132                     }
133                 });
134             }
135             if (meta.functions) {
136                 meta.functions.sort();
137             }
138             if (meta && meta.className.substr(0, 4) !== 'Ext.') {
139                 meta = undefined;
140             }
141             return meta;
142         }
143         if ((code.type === 'AssignmentExpression') &&
144            (code.right.type === 'CallExpression') &&
145            (code.right.callee.type == 'MemberExpression') &&
146            (code.right.callee.object.type == 'Identifier') &&
147            (code.right.callee.object.name == 'Ext') &&
148            (code.right.callee.property.type == 'Identifier') &&
149            (code.right.callee.property.name == 'extend')) {
150             meta.className = '';
151             meta.extend = '';
152             visit(code.left, function (v) {
153                 if (v.name) {
154                     if (meta.className.length > 0)
155                         meta.className += '.';
156                     meta.className += v.name;
157                 }
158             });
159             visit(code.right.arguments[0], function (v) {
160                 if (v.name) {
161                     if (meta.extend.length > 0)
162                         meta.extend += '.';
163                     meta.extend += v.name;
164                 }
165             });
166             properties = code.right.arguments[1].properties;
167             if (properties && properties.length > 0) {
168                 properties.forEach(function (e) {
169                     if ((e.value.type === 'FunctionExpression')) {
170                         if (!meta.functions) {
171                             meta.functions = [];
172                         }
173                         meta.functions.push(e.key.name);
174                     }
175                 });
176             }
177             if (meta.functions) {
178                 meta.functions.sort();
179             }
180             if (meta && meta.className.substr(0, 4) !== 'Ext.') {
181                 meta = undefined;
182             }
183             return meta;
184         }
185         return undefined;
186     };
187
188     // Matches the subtree 'code' with Ext.define('SomeClassName', ...).
189     // Returns the metaclass if successful, otherwise returns undefined.
190     var matchDefine = function (code) {
191         var meta = {},
192             properties;
193         if ((code.type === 'ExpressionStatement') &&
194             (typeof code.expression !== 'undefined') &&
195             (code.expression.type === 'CallExpression') &&
196             (typeof code.expression.callee !== 'undefined') &&
197             (code.expression.callee.type === 'MemberExpression') &&
198             (code.expression.callee.object.type === 'Identifier') &&
199             (code.expression.callee.object.name === 'Ext') &&
200             (code.expression.callee.property.type === 'Identifier') &&
201             (code.expression.callee.property.name === 'define') &&
202             (code.expression.arguments.length >= 2) &&
203             (code.expression.arguments.length <= 3) &&
204             (code.expression.arguments[0].type === 'Literal')) {
205             meta.className = code.expression.arguments[0].value;
206             properties = code.expression.arguments[1].properties;
207             if (properties && properties.length > 0) {
208                 properties.forEach(function (e) {
209                     if ((e.type === 'Property') &&
210                         (e.key !== undefined) &&
211                         (e.value !== undefined) &&
212                         (e.key.type === 'Identifier')) {
213
214                         if ((e.key.name === 'extend') &&
215                             (e.value.type === 'Literal')) {
216                             meta.extend = e.value.value;
217                         }
218
219                         if ((e.key.name === 'alias') &&
220                             (e.value.type === 'Literal')) {
221                             meta.alias = e.value.value;
222                         }
223
224                         if ((e.key.name === 'alias') &&
225                             (e.value !== undefined) &&
226                             (e.value.elements !== undefined) &&
227                             (e.value.elements.length > 0) &&
228                             (e.value.type === 'ArrayExpression')) {
229                             meta.alias = [];
230                             e.value.elements.forEach(function (g) {
231                                 if (g.type === 'Literal') {
232                                     meta.alias.push(g.value);
233                                 }
234                             });
235                         }
236                         if ((e.key.name === 'singleton') &&
237                             (e.value.type === 'Literal')) {
238                             meta.singleton = e.value.value;
239                         }
240
241                         if ((e.key.name === 'alternateClassName') &&
242                             (e.value.type === 'Literal')) {
243                             meta.alternateClassName = e.value.value;
244                         }
245
246                         if ((e.key.name === 'alternateClassName') &&
247                             (e.value !== undefined) &&
248                             (e.value.elements !== undefined) &&
249                             (e.value.elements.length > 0) &&
250                             (e.value.type === 'ArrayExpression')) {
251                             meta.alternateClassName = [];
252                             e.value.elements.forEach(function (g) {
253                                 if (g.type === 'Literal') {
254                                     meta.alternateClassName.push(g.value);
255                                 }
256                             });
257                         }
258
259                         if ((e.key.name === 'requires') &&
260                             (e.value.value !== undefined) &&
261                             (e.value.type === 'Literal')) {
262                             meta.requires = [ e.value.value ];
263                         }
264
265                         if ((e.key.name === 'requires') &&
266                             (e.value !== undefined) &&
267                             (e.value.elements !== undefined) &&
268                             (e.value.elements.length > 0) &&
269                             (e.value.type === 'ArrayExpression')) {
270                             meta.requires = [];
271                             e.value.elements.forEach(function (g) {
272                                 if (g.type === 'Literal') {
273                                     meta.requires.push(g.value);
274                                 }
275                             });
276                         }
277
278                         if ((e.key.name === 'uses') &&
279                             (e.value !== undefined) &&
280                             (e.value.elements !== undefined) &&
281                             (e.value.elements.length > 0) &&
282                             (e.value.type === 'ArrayExpression')) {
283                             meta.uses = [];
284                             e.value.elements.forEach(function (g) {
285                                 if (g.type === 'Literal') {
286                                     meta.uses.push(g.value);
287                                 }
288                             });
289                         }
290
291                         if ((e.key.name === 'mixins') &&
292                             (e.value !== undefined) &&
293                             (e.value.properties !== undefined) &&
294                             (e.value.properties.length > 0) &&
295                             (e.value.type === 'ObjectExpression')) {
296                             meta.mixins = {};
297                             e.value.properties.forEach(function (m) {
298                                 if ((m.type && m.type === 'Property') &&
299                                     (m.key && m.key.type && m.key.type === 'Identifier') &&
300                                     (m.value && m.value.type && m.value.type === 'Literal')) {
301                                     meta.mixins[m.key.name] = m.value.value;
302                                 }
303                             });
304                         }
305
306                         if ((e.value.type === 'FunctionExpression')) {
307                             if (!meta.functions) {
308                                 meta.functions = [];
309                             }
310                             meta.functions.push(e.key.name);
311                         }
312
313                     }
314                 });
315             }
316         }
317         if (meta.functions) {
318             meta.functions.sort();
319         }
320         return meta;
321     };
322
323     var tree = parse(fileName);
324
325     if (typeof tree === 'undefined') {
326         system.print('Warning:', fileName, 'is not a valid JavaScript source');
327         return;
328     }
329
330     visit(tree, function (expr) {
331         var meta = {};
332         meta = matchExtend(expr);
333         if (!meta) {
334             meta = matchDefine(expr);
335         }
336         if (meta && meta.className) {
337             meta.source = fileName;
338             manifest.push(meta);
339         }
340     });
341
342 });
343
344 var out = fs.open(system.args[2], 'w');
345 out.writeLine(JSON.stringify(manifest, undefined, 4));
346 out.close();
347 system.print('Manifest is written to ' + system.args[2] + '.');
348
349 system.print('Finished in ' + (Date.now() - timestamp) / 1000 + ' seconds.');
350
351 system.exit();