api.js 12 KB

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