1 // Creates list of class with its info ("metaclass") in JSON format.
2 // Requirement: hammerjs (see https://github.com/senchalabs/hammerjs).
4 /*global system:true, fs:true, Reflect:true */
6 if (system.args.length !== 3) {
7 system.print('Usage:');
8 system.print(' hammerjs create-manifest.js /path/to/src/ output.json');
12 // Traverses the specified path and collects all *.js files.
13 // Note: the traversal is recursive to all subdirectories.
15 var scanDirectory = function (path) {
18 if (fs.exists(path) && fs.isFile(path) && path.match('.js$')) {
20 } else if (fs.isDirectory(path)) {
21 fs.list(path).forEach(function (e) {
22 subdirs = scanDirectory(path + '/' + e);
23 subdirs.forEach(function (s) {
31 var scanDirectories = function (paths) {
34 paths.split(',').forEach(function (path) {
35 if (path[0] === '-') {
36 excludes.push(path.substring(1));
38 scanDirectory(path).forEach(function (source) {
43 sources = sources.filter(function (e) {
45 excludes.forEach(function (f) {
46 if (e.substring(0, f.length) === f) {
55 var timestamp = Date.now(),
57 sources = scanDirectories(system.args[1]);
59 system.print('Analyzing ' + sources.length + ' source files. Please wait...');
61 sources.forEach(function (fileName) {
63 // Loads the content of a file and returns the syntax tree.
64 var parse = function (fname)
66 var f = fs.open(fname, 'r'),
70 if (line.length === 0) {
76 return Reflect.parse(content);
79 // Recursively visits v and all child objects of v and executes
80 // functions f for each visit.
81 var visit = function (v, f) {
86 if (child !== null && typeof child === 'object') {
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) {
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')) {
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;
117 visit(code.expression.arguments[1], function (v) {
118 if (v.type === 'Identifier') {
119 if (meta.extend.length > 0)
121 meta.extend += v.name;
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) {
131 meta.functions.push(e.key.name);
135 if (meta.functions) {
136 meta.functions.sort();
138 if (meta && meta.className.substr(0, 4) !== 'Ext.') {
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')) {
152 visit(code.left, function (v) {
154 if (meta.className.length > 0)
155 meta.className += '.';
156 meta.className += v.name;
159 visit(code.right.arguments[0], function (v) {
161 if (meta.extend.length > 0)
163 meta.extend += v.name;
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) {
173 meta.functions.push(e.key.name);
177 if (meta.functions) {
178 meta.functions.sort();
180 if (meta && meta.className.substr(0, 4) !== 'Ext.') {
188 // Matches the subtree 'code' with Ext.define('SomeClassName', ...).
189 // Returns the metaclass if successful, otherwise returns undefined.
190 var matchDefine = function (code) {
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')) {
214 if ((e.key.name === 'extend') &&
215 (e.value.type === 'Literal')) {
216 meta.extend = e.value.value;
219 if ((e.key.name === 'alias') &&
220 (e.value.type === 'Literal')) {
221 meta.alias = e.value.value;
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')) {
230 e.value.elements.forEach(function (g) {
231 if (g.type === 'Literal') {
232 meta.alias.push(g.value);
236 if ((e.key.name === 'singleton') &&
237 (e.value.type === 'Literal')) {
238 meta.singleton = e.value.value;
241 if ((e.key.name === 'alternateClassName') &&
242 (e.value.type === 'Literal')) {
243 meta.alternateClassName = e.value.value;
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);
259 if ((e.key.name === 'requires') &&
260 (e.value.value !== undefined) &&
261 (e.value.type === 'Literal')) {
262 meta.requires = [ e.value.value ];
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')) {
271 e.value.elements.forEach(function (g) {
272 if (g.type === 'Literal') {
273 meta.requires.push(g.value);
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')) {
284 e.value.elements.forEach(function (g) {
285 if (g.type === 'Literal') {
286 meta.uses.push(g.value);
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')) {
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;
306 if ((e.value.type === 'FunctionExpression')) {
307 if (!meta.functions) {
310 meta.functions.push(e.key.name);
317 if (meta.functions) {
318 meta.functions.sort();
323 var tree = parse(fileName);
325 if (typeof tree === 'undefined') {
326 system.print('Warning:', fileName, 'is not a valid JavaScript source');
330 visit(tree, function (expr) {
332 meta = matchExtend(expr);
334 meta = matchDefine(expr);
336 if (meta && meta.className) {
337 meta.source = fileName;
344 var out = fs.open(system.args[2], 'w');
345 out.writeLine(JSON.stringify(manifest, undefined, 4));
347 system.print('Manifest is written to ' + system.args[2] + '.');
349 system.print('Finished in ' + (Date.now() - timestamp) / 1000 + ' seconds.');