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