1 | var fs = require('fs-extra');
|
2 | var path = require('path');
|
3 | var spawn = require('child_process').spawn;
|
4 |
|
5 | var async = require('async');
|
6 | var walk = require('walk').walk;
|
7 | var isWindows = process.platform.indexOf('win') === 0;
|
8 |
|
9 | var sourceDir = path.join(__dirname, '..', 'src');
|
10 | var externsDir = path.join(__dirname, '..', 'externs');
|
11 | var externsPaths = [
|
12 | path.join(externsDir, 'olx.js'),
|
13 | path.join(externsDir, 'geojson.js')
|
14 | ];
|
15 | var infoPath = path.join(__dirname, '..', 'build', 'info.json');
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function getBinaryPath(binaryName) {
|
23 | if (isWindows) {
|
24 | binaryName += '.cmd';
|
25 | }
|
26 |
|
27 | var jsdocResolved = require.resolve('jsdoc/jsdoc.js');
|
28 | var expectedPaths = [
|
29 | path.join(__dirname, '..', 'node_modules', '.bin', binaryName),
|
30 | path.resolve(path.join(path.dirname(jsdocResolved), '..', '.bin', binaryName))
|
31 | ];
|
32 |
|
33 | for (var i = 0; i < expectedPaths.length; i++) {
|
34 | var expectedPath = expectedPaths[i];
|
35 | if (fs.existsSync(expectedPath)) {
|
36 | return expectedPath;
|
37 | }
|
38 | }
|
39 |
|
40 | throw Error('JsDoc binary was not found in any of the expected paths: ' + expectedPaths);
|
41 | }
|
42 |
|
43 | var jsdoc = getBinaryPath('jsdoc');
|
44 |
|
45 | var jsdocConfig = path.join(
|
46 | __dirname, '..', 'config', 'jsdoc', 'info', 'conf.json');
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | function getInfoTime(callback) {
|
55 | fs.stat(infoPath, function(err, stats) {
|
56 | if (err) {
|
57 | if (err.code === 'ENOENT') {
|
58 | callback(null, new Date(0));
|
59 | } else {
|
60 | callback(err);
|
61 | }
|
62 | } else {
|
63 | callback(null, stats.mtime);
|
64 | }
|
65 | });
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function getNewerExterns(date, callback) {
|
77 | var newer = false;
|
78 | var walker = walk(externsDir);
|
79 | walker.on('file', function(root, stats, next) {
|
80 | var sourcePath = path.join(root, stats.name);
|
81 | externsPaths.forEach(function(path) {
|
82 | if (sourcePath === path && stats.mtime > date) {
|
83 | newer = true;
|
84 | }
|
85 | });
|
86 | next();
|
87 | });
|
88 | walker.on('errors', function() {
|
89 | callback(new Error('Trouble walking ' + externsDir));
|
90 | });
|
91 | walker.on('end', function() {
|
92 | callback(null, date, newer);
|
93 | });
|
94 | }
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 | function getNewer(date, newer, callback) {
|
106 | var paths = [].concat(externsPaths);
|
107 |
|
108 | var walker = walk(sourceDir);
|
109 | walker.on('file', function(root, stats, next) {
|
110 | var sourcePath = path.join(root, stats.name);
|
111 | if (/\.js$/.test(sourcePath)) {
|
112 | paths.push(sourcePath);
|
113 | if (stats.mtime > date) {
|
114 | newer = true;
|
115 | }
|
116 | }
|
117 | next();
|
118 | });
|
119 | walker.on('errors', function() {
|
120 | callback(new Error('Trouble walking ' + sourceDir));
|
121 | });
|
122 | walker.on('end', function() {
|
123 |
|
124 | |
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | if (isWindows) {
|
131 | paths = [sourceDir].concat(externsPaths);
|
132 | }
|
133 |
|
134 | callback(null, newer ? paths : []);
|
135 | });
|
136 | }
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | function parseOutput(output) {
|
145 | if (!output) {
|
146 | throw new Error('Expected JSON output');
|
147 | }
|
148 |
|
149 | var info;
|
150 | try {
|
151 | info = JSON.parse(String(output));
|
152 | } catch (err) {
|
153 | throw new Error('Failed to parse output as JSON: ' + output);
|
154 | }
|
155 | if (!Array.isArray(info.symbols)) {
|
156 | throw new Error('Expected symbols array: ' + output);
|
157 | }
|
158 | if (!Array.isArray(info.defines)) {
|
159 | throw new Error('Expected defines array: ' + output);
|
160 | }
|
161 |
|
162 | return info;
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | function spawnJSDoc(paths, callback) {
|
174 | if (paths.length === 0) {
|
175 | process.nextTick(function() {
|
176 | callback(null, null);
|
177 | });
|
178 | return;
|
179 | }
|
180 |
|
181 | var output = '';
|
182 | var errors = '';
|
183 | var cwd = path.join(__dirname, '..');
|
184 | var child = spawn(jsdoc, ['-c', jsdocConfig].concat(paths), {cwd: cwd});
|
185 |
|
186 | child.stdout.on('data', function(data) {
|
187 | output += String(data);
|
188 | });
|
189 |
|
190 | child.stderr.on('data', function(data) {
|
191 | errors += String(data);
|
192 | });
|
193 |
|
194 | child.on('exit', function(code) {
|
195 | if (code) {
|
196 | callback(new Error(errors || 'JSDoc failed with no output'));
|
197 | } else {
|
198 | var info;
|
199 | try {
|
200 | info = parseOutput(output);
|
201 | } catch (err) {
|
202 | callback(err);
|
203 | return;
|
204 | }
|
205 | callback(null, info);
|
206 | }
|
207 | });
|
208 | }
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | var getProvides = async.memoize(function(srcPath, callback) {
|
218 | fs.readFile(srcPath, function(err, data) {
|
219 | if (err) {
|
220 | callback(err);
|
221 | return;
|
222 | }
|
223 | var provides = [];
|
224 | var matcher = /goog\.provide\('(.*)'\)/;
|
225 | String(data).split('\n').forEach(function(line) {
|
226 | var match = line.match(matcher);
|
227 | if (match) {
|
228 | provides.push(match[1]);
|
229 | }
|
230 | });
|
231 | callback(null, provides);
|
232 | });
|
233 | });
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | function addSymbolProvides(info, callback) {
|
242 | if (!info) {
|
243 | process.nextTick(function() {
|
244 | callback(null, null);
|
245 | });
|
246 | return;
|
247 | }
|
248 |
|
249 | function addProvides(symbol, callback) {
|
250 | getProvides(symbol.path, function(err, provides) {
|
251 | if (err) {
|
252 | callback(err);
|
253 | return;
|
254 | }
|
255 | symbol.provides = provides;
|
256 | callback(null, symbol);
|
257 | });
|
258 | }
|
259 |
|
260 | async.map(info.symbols, addProvides, function(err, newSymbols) {
|
261 | info.symbols = newSymbols;
|
262 | callback(err, info);
|
263 | });
|
264 | }
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 | function writeInfo(info, callback) {
|
273 | if (info) {
|
274 | var str = JSON.stringify(info, null, ' ');
|
275 | fs.outputFile(infoPath, str, callback);
|
276 | } else {
|
277 | process.nextTick(function() {
|
278 | callback(null);
|
279 | });
|
280 | }
|
281 | }
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 | function main(callback) {
|
292 | async.waterfall([
|
293 | getInfoTime,
|
294 | getNewerExterns,
|
295 | getNewer,
|
296 | spawnJSDoc,
|
297 | addSymbolProvides,
|
298 | writeInfo
|
299 | ], callback);
|
300 | }
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | if (require.main === module) {
|
308 | main(function(err) {
|
309 | if (err) {
|
310 | process.stderr.write(err.message + '\n');
|
311 | process.exit(1);
|
312 | } else {
|
313 | process.exit(0);
|
314 | }
|
315 | });
|
316 | }
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | module.exports = main;
|