source: frontend/node_modules/jake/lib/task/task.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 13 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 12.2 KB
Line 
1let EventEmitter = require('events').EventEmitter;
2let async = require('async');
3let pc = require('picocolors');
4// 'rule' module is required at the bottom because circular deps
5
6// Used for task value, so better not to use
7// null, since value should be unset/uninitialized
8let UNDEFINED_VALUE;
9
10const ROOT_TASK_NAME = '__rootTask__';
11const POLLING_INTERVAL = 100;
12
13// Parse any positional args attached to the task-name
14function parsePrereqName(name) {
15 let taskArr = name.split('[');
16 let taskName = taskArr[0];
17 let taskArgs = [];
18 if (taskArr[1]) {
19 taskArgs = taskArr[1].replace(/\]$/, '');
20 taskArgs = taskArgs.split(',');
21 }
22 return {
23 name: taskName,
24 args: taskArgs
25 };
26}
27
28/**
29 @name jake.Task
30 @class
31 @extends EventEmitter
32 @description A Jake Task
33
34 @param {String} name The name of the Task
35 @param {Array} [prereqs] Prerequisites to be run before this task
36 @param {Function} [action] The action to perform for this task
37 @param {Object} [opts]
38 @param {Array} [opts.asyc=false] Perform this task asynchronously.
39 If you flag a task with this option, you must call the global
40 `complete` method inside the task's action, for execution to proceed
41 to the next task.
42 */
43class Task extends EventEmitter {
44
45 constructor(name, prereqs, action, options) {
46 // EventEmitter ctor takes no args
47 super();
48
49 if (name.indexOf(':') > -1) {
50 throw new Error('Task name cannot include a colon. It is used internally as namespace delimiter.');
51 }
52 let opts = options || {};
53
54 this._currentPrereqIndex = 0;
55 this._internal = false;
56 this._skipped = false;
57
58 this.name = name;
59 this.prereqs = prereqs;
60 this.action = action;
61 this.async = false;
62 this.taskStatus = Task.runStatuses.UNSTARTED;
63 this.description = null;
64 this.args = [];
65 this.value = UNDEFINED_VALUE;
66 this.concurrency = 1;
67 this.startTime = null;
68 this.endTime = null;
69 this.directory = null;
70 this.namespace = null;
71
72 // Support legacy async-flag -- if not explicitly passed or falsy, will
73 // be set to empty-object
74 if (typeof opts == 'boolean' && opts === true) {
75 this.async = true;
76 }
77 else {
78 if (opts.async) {
79 this.async = true;
80 }
81 if (opts.concurrency) {
82 this.concurrency = opts.concurrency;
83 }
84 }
85
86 //Do a test on self dependencies for this task
87 if(Array.isArray(this.prereqs) && this.prereqs.indexOf(this.name) !== -1) {
88 throw new Error("Cannot use prereq " + this.name + " as a dependency of itself");
89 }
90 }
91
92 get fullName() {
93 return this._getFullName();
94 }
95
96 get params() {
97 return this._getParams();
98 }
99
100 _initInvocationChain() {
101 // Legacy global invocation chain
102 jake._invocationChain.push(this);
103
104 // New root chain
105 if (!this._invocationChain) {
106 this._invocationChainRoot = true;
107 this._invocationChain = [];
108 if (jake.currentRunningTask) {
109 jake.currentRunningTask._waitForChains = jake.currentRunningTask._waitForChains || [];
110 jake.currentRunningTask._waitForChains.push(this._invocationChain);
111 }
112 }
113 }
114
115 /**
116 @name jake.Task#invoke
117 @function
118 @description Runs prerequisites, then this task. If the task has already
119 been run, will not run the task again.
120 */
121 invoke() {
122 this._initInvocationChain();
123
124 this.args = Array.prototype.slice.call(arguments);
125 this.reenabled = false;
126 this.runPrereqs();
127 }
128
129 /**
130 @name jake.Task#execute
131 @function
132 @description Run only this task, without prereqs. If the task has already
133 been run, *will* run the task again.
134 */
135 execute() {
136 this._initInvocationChain();
137
138 this.args = Array.prototype.slice.call(arguments);
139 this.reenable();
140 this.reenabled = true;
141 this.run();
142 }
143
144 runPrereqs() {
145 if (this.prereqs && this.prereqs.length) {
146
147 if (this.concurrency > 1) {
148 async.eachLimit(this.prereqs, this.concurrency,
149
150 (name, cb) => {
151 let parsed = parsePrereqName(name);
152
153 let prereq = this.namespace.resolveTask(parsed.name) ||
154 jake.attemptRule(name, this.namespace, 0) ||
155 jake.createPlaceholderFileTask(name, this.namespace);
156
157 if (!prereq) {
158 throw new Error('Unknown task "' + name + '"');
159 }
160
161 //Test for circular invocation
162 if(prereq === this) {
163 setImmediate(function () {
164 cb(new Error("Cannot use prereq " + prereq.name + " as a dependency of itself"));
165 });
166 }
167
168 if (prereq.taskStatus == Task.runStatuses.DONE) {
169 //prereq already done, return
170 setImmediate(cb);
171 }
172 else {
173 //wait for complete before calling cb
174 prereq.once('_done', () => {
175 prereq.removeAllListeners('_done');
176 setImmediate(cb);
177 });
178 // Start the prereq if we are the first to encounter it
179 if (prereq.taskStatus === Task.runStatuses.UNSTARTED) {
180 prereq.taskStatus = Task.runStatuses.STARTED;
181 prereq.invoke.apply(prereq, parsed.args);
182 }
183 }
184 },
185
186 (err) => {
187 //async callback is called after all prereqs have run.
188 if (err) {
189 throw err;
190 }
191 else {
192 setImmediate(this.run.bind(this));
193 }
194 }
195 );
196 }
197 else {
198 setImmediate(this.nextPrereq.bind(this));
199 }
200 }
201 else {
202 setImmediate(this.run.bind(this));
203 }
204 }
205
206 nextPrereq() {
207 let self = this;
208 let index = this._currentPrereqIndex;
209 let name = this.prereqs[index];
210 let prereq;
211 let parsed;
212
213 if (name) {
214
215 parsed = parsePrereqName(name);
216
217 prereq = this.namespace.resolveTask(parsed.name) ||
218 jake.attemptRule(name, this.namespace, 0) ||
219 jake.createPlaceholderFileTask(name, this.namespace);
220
221 if (!prereq) {
222 throw new Error('Unknown task "' + name + '"');
223 }
224
225 // Do when done
226 if (prereq.taskStatus == Task.runStatuses.DONE) {
227 self.handlePrereqDone(prereq);
228 }
229 else {
230 prereq.once('_done', () => {
231 this.handlePrereqDone(prereq);
232 prereq.removeAllListeners('_done');
233 });
234 if (prereq.taskStatus == Task.runStatuses.UNSTARTED) {
235 prereq.taskStatus = Task.runStatuses.STARTED;
236 prereq._invocationChain = this._invocationChain;
237 prereq.invoke.apply(prereq, parsed.args);
238 }
239 }
240 }
241 }
242
243 /**
244 @name jake.Task#reenable
245 @function
246 @description Reenables a task so that it can be run again.
247 */
248 reenable(deep) {
249 let prereqs;
250 let prereq;
251 this._skipped = false;
252 this.taskStatus = Task.runStatuses.UNSTARTED;
253 this.value = UNDEFINED_VALUE;
254 if (deep && this.prereqs) {
255 prereqs = this.prereqs;
256 for (let i = 0, ii = prereqs.length; i < ii; i++) {
257 prereq = jake.Task[prereqs[i]];
258 if (prereq) {
259 prereq.reenable(deep);
260 }
261 }
262 }
263 }
264
265 handlePrereqDone(prereq) {
266 this._currentPrereqIndex++;
267 if (this._currentPrereqIndex < this.prereqs.length) {
268 setImmediate(this.nextPrereq.bind(this));
269 }
270 else {
271 setImmediate(this.run.bind(this));
272 }
273 }
274
275 isNeeded() {
276 let needed = true;
277 if (this.taskStatus == Task.runStatuses.DONE) {
278 needed = false;
279 }
280 return needed;
281 }
282
283 run() {
284 let val, previous;
285 let hasAction = typeof this.action == 'function';
286
287 if (!this.isNeeded()) {
288 this.emit('skip');
289 this.emit('_done');
290 }
291 else {
292 if (this._invocationChain.length) {
293 previous = this._invocationChain[this._invocationChain.length - 1];
294 // If this task is repeating and its previous is equal to this, don't check its status because it was set to UNSTARTED by the reenable() method
295 if (!(this.reenabled && previous == this)) {
296 if (previous.taskStatus != Task.runStatuses.DONE) {
297 let now = (new Date()).getTime();
298 if (now - this.startTime > jake._taskTimeout) {
299 return jake.fail(`Timed out waiting for task: ${previous.name} with status of ${previous.taskStatus}`);
300 }
301 setTimeout(this.run.bind(this), POLLING_INTERVAL);
302 return;
303 }
304 }
305 }
306 if (!(this.reenabled && previous == this)) {
307 this._invocationChain.push(this);
308 }
309
310 if (!(this._internal || jake.program.opts.quiet)) {
311 jake.emit('started', {
312 name: this.fullName,
313 task: this,
314 });
315 console.log("Starting '" + pc.green(this.fullName) + "'...");
316 }
317
318 this.startTime = (new Date()).getTime();
319 this.emit('start');
320
321 jake.currentRunningTask = this;
322
323 if (hasAction) {
324 try {
325 if (this.directory) {
326 process.chdir(this.directory);
327 }
328
329 val = this.action.apply(this, this.args);
330
331 if (typeof val == 'object' && typeof val.then == 'function') {
332 this.async = true;
333
334 val.then(
335 (result) => {
336 setImmediate(() => {
337 this.complete(result);
338 });
339 },
340 (err) => {
341 setImmediate(() => {
342 this.errorOut(err);
343 });
344 });
345 }
346 }
347 catch (err) {
348 this.errorOut(err);
349 return; // Bail out, not complete
350 }
351 }
352
353 if (!(hasAction && this.async)) {
354 setImmediate(() => {
355 this.complete(val);
356 });
357 }
358 }
359 }
360
361 errorOut(err) {
362 this.taskStatus = Task.runStatuses.ERROR;
363 this._invocationChain.chainStatus = Task.runStatuses.ERROR;
364 this.emit('error', err);
365 }
366
367 complete(val) {
368
369 if (Array.isArray(this._waitForChains)) {
370 let stillWaiting = this._waitForChains.some((chain) => {
371 return !(chain.chainStatus == Task.runStatuses.DONE ||
372 chain.chainStatus == Task.runStatuses.ERROR);
373 });
374 if (stillWaiting) {
375 let now = (new Date()).getTime();
376 let elapsed = now - this.startTime;
377 if (elapsed > jake._taskTimeout) {
378 return jake.fail(`Timed out waiting for task: ${this.name} with status of ${this.taskStatus}. Elapsed: ${elapsed}`);
379 }
380 setTimeout(() => {
381 this.complete(val);
382 }, POLLING_INTERVAL);
383 return;
384 }
385 }
386
387 jake._invocationChain.splice(jake._invocationChain.indexOf(this), 1);
388
389 if (this._invocationChainRoot) {
390 this._invocationChain.chainStatus = Task.runStatuses.DONE;
391 }
392
393 this._currentPrereqIndex = 0;
394
395 // If 'complete' getting called because task has been
396 // run already, value will not be passed -- leave in place
397 if (!this._skipped) {
398 this.taskStatus = Task.runStatuses.DONE;
399 this.value = val;
400
401 this.emit('complete', this.value);
402 this.emit('_done');
403
404 this.endTime = (new Date()).getTime();
405 let taskTime = this.endTime - this.startTime;
406
407 if (!(this._internal || jake.program.opts.quiet)) {
408 jake.emit('finished', {
409 name: this.fullName,
410 task: this,
411 time: taskTime,
412 });
413 console.log("Finished '" + pc.green(this.fullName) + "' after " + pc.magenta(taskTime + ' ms'));
414 }
415
416 }
417 }
418
419 _getFullName() {
420 let ns = this.namespace;
421 let path = (ns && ns.path) || '';
422 path = (path && path.split(':')) || [];
423 if (this.namespace !== jake.defaultNamespace) {
424 path.push(this.namespace.name);
425 }
426 path.push(this.name);
427 return path.join(':');
428 }
429
430 _getParams() {
431 if (!this.action) return "";
432 let params = (new RegExp('(?:'+this.action.name+'\\s*|^)\\s*\\((.*?)\\)').exec(this.action.toString().replace(/\n/g, '')) || [''])[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
433 return params;
434 }
435
436 static getBaseNamespacePath(fullName) {
437 return fullName.split(':').slice(0, -1).join(':');
438 }
439
440 static getBaseTaskName(fullName) {
441 return fullName.split(':').pop();
442 }
443}
444
445Task.runStatuses = {
446 UNSTARTED: 'unstarted',
447 DONE: 'done',
448 STARTED: 'started',
449 ERROR: 'error'
450};
451
452Task.ROOT_TASK_NAME = ROOT_TASK_NAME;
453
454exports.Task = Task;
455
456// Required here because circular deps
457require('../rule');
458
Note: See TracBrowser for help on using the repository browser.