1/*
2 * Jake JavaScript build tool
3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *         http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17*/
18let { uuid } = require('./utils');
19
20let api = new (function () {
21  /**
22    @name task
23    @static
24    @function
25    @description Creates a Jake Task
26    `
27    @param {String} name The name of the Task
28    @param {Array} [prereqs] Prerequisites to be run before this task
29    @param {Function} [action] The action to perform for this task
30    @param {Object} [opts]
31      @param {Boolean} [opts.asyc=false] Perform this task asynchronously.
32      If you flag a task with this option, you must call the global
33      `complete` method inside the task's action, for execution to proceed
34      to the next task.
35
36    @example
37    desc('This is the default task.');
38    task('default', function (params) {
39      console.log('This is the default task.');
40    });
41
42    desc('This task has prerequisites.');
43    task('hasPrereqs', ['foo', 'bar', 'baz'], function (params) {
44      console.log('Ran some prereqs first.');
45    });
46
47    desc('This is an asynchronous task.');
48    task('asyncTask', function () {
49      setTimeout(complete, 1000);
50    }, {async: true});
51   */
52  this.task = function (name, prereqs, action, opts) {
53    let args = Array.prototype.slice.call(arguments);
54    let createdTask;
55    args.unshift('task');
56    createdTask = jake.createTask.apply(global, args);
57    jake.currentTaskDescription = null;
58    return createdTask;
59  };
60
61  /**
62    @name rule
63    @static
64    @function
65    @description Creates a Jake Suffix Rule
66    `
67    @param {String} pattern The suffix name of the objective
68    @param {String} source The suffix name of the objective
69    @param {Array} [prereqs] Prerequisites to be run before this task
70    @param {Function} [action] The action to perform for this task
71    @param {Object} [opts]
72      @param {Boolean} [opts.asyc=false] Perform this task asynchronously.
73      If you flag a task with this option, you must call the global
74      `complete` method inside the task's action, for execution to proceed
75      to the next task.
76    @example
77    desc('This is a rule, which does not support namespace or pattern.');
78    rule('.o', '.c', {async: true}, function () {
79      let cmd = util.format('gcc -o %s %s', this.name, this.source);
80      jake.exec([cmd], function () {
81        complete();
82      }, {printStdout: true});
83    });
84
85    desc('This rule has prerequisites.');
86    rule('.o', '.c', ['util.h'], {async: true}, function () {
87      let cmd = util.format('gcc -o %s %s', this.name, this.source);
88      jake.exec([cmd], function () {
89        complete();
90      }, {printStdout: true});
91    });
92
93    desc('This is a rule with patterns.');
94    rule('%.o', '%.c', {async: true}, function () {
95      let cmd = util.format('gcc -o %s %s', this.name, this.source);
96      jake.exec([cmd], function () {
97        complete();
98      }, {printStdout: true});
99    });
100
101    desc('This is another rule with patterns.');
102    rule('obj/%.o', 'src/%.c', {async: true}, function () {
103      let cmd = util.format('gcc -o %s %s', this.name, this.source);
104      jake.exec([cmd], function () {
105        complete();
106      }, {printStdout: true});
107    });
108
109    desc('This is an example with chain rules.');
110    rule('%.pdf', '%.dvi', {async: true}, function () {
111      let cmd = util.format('dvipdfm %s',this.source);
112      jake.exec([cmd], function () {
113        complete();
114      }, {printStdout: true});
115    });
116
117    rule('%.dvi', '%.tex', {async: true}, function () {
118      let cmd = util.format('latex %s',this.source);
119      jake.exec([cmd], function () {
120        complete();
121      }, {printStdout: true});
122    });
123
124    desc('This rule has a namespace.');
125    task('default', ['debug:obj/main.o]);
126
127    namespace('debug', {async: true}, function() {
128      rule('obj/%.o', 'src/%.c', function () {
129        // ...
130      });
131    }
132   */
133  this.rule = function () {
134    let args = Array.prototype.slice.call(arguments);
135    let arg;
136    let pattern = args.shift();
137    let source = args.shift();
138    let prereqs = [];
139    let action = function () {};
140    let opts = {};
141    let key = pattern.toString(); // May be a RegExp
142
143    while ((arg = args.shift())) {
144      if (typeof arg == 'function') {
145        action = arg;
146      }
147      else if (Array.isArray(arg)) {
148        prereqs = arg;
149      }
150      else {
151        opts = arg;
152      }
153    }
154
155    jake.currentNamespace.rules[key] = new jake.Rule({
156      pattern: pattern,
157      source: source,
158      prereqs: prereqs,
159      action: action,
160      opts: opts,
161      desc: jake.currentTaskDescription,
162      ns: jake.currentNamespace
163    });
164    jake.currentTaskDescription = null;
165  };
166
167  /**
168    @name directory
169    @static
170    @function
171    @description Creates a Jake DirectoryTask. Can be used as a prerequisite
172    for FileTasks, or for simply ensuring a directory exists for use with a
173    Task's action.
174    `
175    @param {String} name The name of the DiretoryTask
176
177    @example
178
179    // Creates the package directory for distribution
180    directory('pkg');
181   */
182  this.directory = function (name) {
183    let args = Array.prototype.slice.call(arguments);
184    let createdTask;
185    args.unshift('directory');
186    createdTask = jake.createTask.apply(global, args);
187    jake.currentTaskDescription = null;
188    return createdTask;
189  };
190
191  /**
192    @name file
193    @static
194    @function
195    @description Creates a Jake FileTask.
196    `
197    @param {String} name The name of the FileTask
198    @param {Array} [prereqs] Prerequisites to be run before this task
199    @param {Function} [action] The action to create this file, if it doesn't
200    exist already.
201    @param {Object} [opts]
202      @param {Array} [opts.asyc=false] Perform this task asynchronously.
203      If you flag a task with this option, you must call the global
204      `complete` method inside the task's action, for execution to proceed
205      to the next task.
206
207   */
208  this.file = function (name, prereqs, action, opts) {
209    let args = Array.prototype.slice.call(arguments);
210    let createdTask;
211    args.unshift('file');
212    createdTask = jake.createTask.apply(global, args);
213    jake.currentTaskDescription = null;
214    return createdTask;
215  };
216
217  /**
218    @name desc
219    @static
220    @function
221    @description Creates a description for a Jake Task (or FileTask,
222    DirectoryTask). When invoked, the description that iscreated will
223    be associated with whatever Task is created next.
224    `
225    @param {String} description The description for the Task
226   */
227  this.desc = function (description) {
228    jake.currentTaskDescription = description;
229  };
230
231  /**
232    @name namespace
233    @static
234    @function
235    @description Creates a namespace which allows logical grouping
236    of tasks, and prevents name-collisions with task-names. Namespaces
237    can be nested inside of other namespaces.
238    `
239    @param {String} name The name of the namespace
240    @param {Function} scope The enclosing scope for the namespaced tasks
241
242    @example
243    namespace('doc', function () {
244      task('generate', ['doc:clobber'], function () {
245        // Generate some docs
246      });
247
248      task('clobber', function () {
249        // Clobber the doc directory first
250      });
251    });
252   */
253  this.namespace = function (name, closure) {
254    let curr = jake.currentNamespace;
255    let ns = curr.childNamespaces[name] || new jake.Namespace(name, curr);
256    let fn = closure || function () {};
257    curr.childNamespaces[name] = ns;
258    jake.currentNamespace = ns;
259    fn();
260    jake.currentNamespace = curr;
261    jake.currentTaskDescription = null;
262    return ns;
263  };
264
265  /**
266    @name complete
267    @static
268    @function
269    @description Completes an asynchronous task, allowing Jake's
270    execution to proceed to the next task. Calling complete globally or without
271    arguments completes the last task on the invocationChain. If you use parallel
272    execution of prereqs this will probably complete a wrong task. You should call this
273    function with this task as the first argument, before the optional return value.
274    Alternatively you can call task.complete()
275    `
276    @example
277    task('generate', ['doc:clobber'], function () {
278      exec('./generate_docs.sh', function (err, stdout, stderr) {
279        if (err || stderr) {
280          fail(err || stderr);
281        }
282        else {
283          console.log(stdout);
284          complete();
285        }
286      });
287    }, {async: true});
288   */
289  this.complete = function (task, val) {
290    //this should detect if the first arg is a task, but I guess it should be more thorough
291    if(task && task. _currentPrereqIndex >=0 ) {
292      task.complete(val);
293    }
294    else {
295      val = task;
296      if(jake._invocationChain.length > 0) {
297        jake._invocationChain[jake._invocationChain.length-1].complete(val);
298      }
299    }
300  };
301
302  /**
303    @name fail
304    @static
305    @function
306    @description Causes Jake execution to abort with an error.
307    Allows passing an optional error code, which will be used to
308    set the exit-code of exiting process.
309    `
310    @param {Error|String} err The error to thow when aborting execution.
311    If this argument is an Error object, it will simply be thrown. If
312    a String, it will be used as the error-message. (If it is a multi-line
313    String, the first line will be used as the Error message, and the
314    remaining lines will be used as the error-stack.)
315
316    @example
317    task('createTests, function () {
318      if (!fs.existsSync('./tests')) {
319        fail('Test directory does not exist.');
320      }
321      else {
322        // Do some testing stuff ...
323      }
324    });
325   */
326  this.fail = function (err, code) {
327    let msg;
328    let errObj;
329    if (code) {
330      jake.errorCode = code;
331    }
332    if (err) {
333      if (typeof err == 'string') {
334        // Use the initial or only line of the error as the error-message
335        // If there was a multi-line error, use the rest as the stack
336        msg = err.split('\n');
337        errObj = new Error(msg.shift());
338        if (msg.length) {
339          errObj.stack = msg.join('\n');
340        }
341        throw errObj;
342      }
343      else if (err instanceof Error) {
344        throw err;
345      }
346      else {
347        throw new Error(err.toString());
348      }
349    }
350    else {
351      throw new Error();
352    }
353  };
354
355  this.packageTask = function (name, version, prereqs, definition) {
356    return new jake.PackageTask(name, version, prereqs, definition);
357  };
358
359  this.publishTask = function (name, prereqs, opts, definition) {
360    return new jake.PublishTask(name, prereqs, opts, definition);
361  };
362
363  // Backward-compat
364  this.npmPublishTask = function (name, prereqs, opts, definition) {
365    return new jake.PublishTask(name, prereqs, opts, definition);
366  };
367
368  this.testTask = function () {
369    let ctor = function () {};
370    let t;
371    ctor.prototype = jake.TestTask.prototype;
372    t = new ctor();
373    jake.TestTask.apply(t, arguments);
374    return t;
375  };
376
377  this.setTaskTimeout = function (t) {
378    this._taskTimeout = t;
379  };
380
381  this.setSeriesAutoPrefix = function (prefix) {
382    this._seriesAutoPrefix = prefix;
383  };
384
385  this.series = function (...args) {
386    let prereqs = args.map((arg) => {
387      let name = (this._seriesAutoPrefix || '') + arg.name;
388      jake.task(name, arg);
389      return name;
390    });
391    let seriesName = uuid();
392    let seriesTask = jake.task(seriesName, prereqs);
393    seriesTask._internal = true;
394    let res = function () {
395      return new Promise((resolve) => {
396        seriesTask.invoke();
397        seriesTask.on('complete', (val) => {
398          resolve(val);
399        });
400      });
401    };
402    Object.defineProperty(res, 'name', {value: uuid(),
403      writable: false});
404    return res;
405  };
406
407})();
408
409module.exports = api;
410