UNPKG

beelzebub

Version:
1,997 lines (1,691 loc) 62.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>bzTasks.js - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc.css"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="BzTasks.html">BzTasks</a><ul class='methods'><li data-type='method'><a href="BzTasks.html#$addSubTasks">$addSubTasks</a></li><li data-type='method'><a href="BzTasks.html#$defineTaskVars">$defineTaskVars</a></li><li data-type='method'><a href="BzTasks.html#$getGlobalVars">$getGlobalVars</a></li><li data-type='method'><a href="BzTasks.html#$getName">$getName</a></li><li data-type='method'><a href="BzTasks.html#$getRunning">$getRunning</a></li><li data-type='method'><a href="BzTasks.html#$getStatsSummary">$getStatsSummary</a></li><li data-type='method'><a href="BzTasks.html#$getSubTask">$getSubTask</a></li><li data-type='method'><a href="BzTasks.html#$getSubTasks">$getSubTasks</a></li><li data-type='method'><a href="BzTasks.html#$getTask">$getTask</a></li><li data-type='method'><a href="BzTasks.html#$getTaskFlatList">$getTaskFlatList</a></li><li data-type='method'><a href="BzTasks.html#$getTaskTree">$getTaskTree</a></li><li data-type='method'><a href="BzTasks.html#$getVarDefsForTaskName">$getVarDefsForTaskName</a></li><li data-type='method'><a href="BzTasks.html#$hasRunBefore">$hasRunBefore</a></li><li data-type='method'><a href="BzTasks.html#$hasSubTask">$hasSubTask</a></li><li data-type='method'><a href="BzTasks.html#$hasTask">$hasTask</a></li><li data-type='method'><a href="BzTasks.html#$isRoot">$isRoot</a></li><li data-type='method'><a href="BzTasks.html#$parallel">$parallel</a></li><li data-type='method'><a href="BzTasks.html#$printHelp">$printHelp</a></li><li data-type='method'><a href="BzTasks.html#$register">$register</a></li><li data-type='method'><a href="BzTasks.html#$run">$run</a></li><li data-type='method'><a href="BzTasks.html#$sequence">$sequence</a></li><li data-type='method'><a href="BzTasks.html#$setDefault">$setDefault</a></li><li data-type='method'><a href="BzTasks.html#$setGlobalVars">$setGlobalVars</a></li><li data-type='method'><a href="BzTasks.html#$setName">$setName</a></li><li data-type='method'><a href="BzTasks.html#$setSubTask">$setSubTask</a></li><li data-type='method'><a href="BzTasks.html#$setSubTasks">$setSubTasks</a></li><li data-type='method'><a href="BzTasks.html#$setTaskHelpDocs">$setTaskHelpDocs</a></li><li data-type='method'><a href="BzTasks.html#$useAsRoot">$useAsRoot</a></li></ul></li></ul><h3>Interfaces</h3><ul><li><a href="BzTasks_$afterAll.html">$afterAll</a></li><li><a href="BzTasks_$afterEach.html">$afterEach</a></li><li><a href="BzTasks_$beforeAll.html">$beforeAll</a></li><li><a href="BzTasks_$beforeEach.html">$beforeEach</a></li><li><a href="BzTasks_$init.html">$init</a></li></ul> </nav> <div id="main"> <h1 class="page-title">bzTasks.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>'use strict'; const _ = require('lodash'); const when = require('when'); const co = require('co'); const chalk = require('chalk'); const whenSeq = require('when/sequence'); const streamToPromise = require('stream-to-promise'); const manifest = require('../package.json'); const bzStats = require('./bzStats.js'); const util = require('./util.js'); /** * Beelzebub Task Class, should be extended * @class */ class BzTasks { constructor (config, hidden = false) { this._hidden = hidden; this.beelzebub = config.beelzebub || util.getInstance(); util.processConfig(config, this.beelzebub.getConfig(), this); this.name = config.name || this.constructor.name || 'BzTasks'; this.version = manifest.version; this.namePath = this._buildNamePath(config); // this.vLogger.log('constructor namePath:', this.namePath, ', name:', this.name); // TODO: use config function/util to process this this._rootLevel = false; this._defaultTaskFuncName = this.$defaultTask || null; this._tasks = {}; this._subTasks = {}; this._running = null; this._beforeAllRun = false; this._stats = new bzStats.Task(); // TODO: add cli options/commands } /** * Util to build Name Path * TODO: move this out of the class * @private */ _buildNamePath (config) { let namePath = this.name; if (config.parentPath) { namePath = config.parentPath + '.' + namePath; } return namePath; } /** * Get Task Tree starting with this task * @returns {object} */ $getTaskTree () { let tree = { name: this.name, tasks: this._tasks, stats: this._stats, subTasks: [] }; let i = 0; _.forEach(this.$getSubTasks(), (task) => { // if task is suppose to be hidden ($root$ for example) // use first sub task as tree if (this._hidden &amp;&amp; i === 0) { tree = task.$getTaskTree(); } else { if (this._hidden) { // this should not really happen this.logger.warn('multi sub tasks in hidden task node not allowed'); } tree.subTasks.push(task.$getTaskTree()); } i++; }); return tree; } /** * Get flatten task tree so it's one level * @returns {object} */ $getTaskFlatList () { let list = []; if (!this._hidden) { list.push({ name: this.name, tasks: this._tasks, stats: this._stats }); } _.forEach(this.$getSubTasks(), (task) => { list = list.concat(task.$getTaskFlatList()); }); return list; } /** * Get task status and all it's sub tasks stats * @returns {object} */ $getStatsSummary (parentSummary) { let summary = this._stats.getSummary(parentSummary); _.forEach(this.$getSubTasks(), (task) => { summary = task.$getStatsSummary(summary); }); return summary; } /** * Prints Task help and all sub tasks help * @example const Beelzebub = require('../../'); const bz = Beelzebub(options || { verbose: true }); class MyTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$setDefault('task1'); this.$setTaskHelpDocs('task1', 'ES7 Decorator Example MyTasks - Task 1'); this.$setTaskHelpDocs('task2', 'ES7 Decorator Example MyTasks - Task 2'); } task1 () { this.logger.log('MyTasks task1'); } task2 () { this.logger.log('MyTasks task2'); } } bz.add(MyTasks); class MyTasks2 extends bz.Tasks { constructor (config) { super(config); this.$setDefault('task1'); this.$setTaskHelpDocs('task1', 'ES7 Decorator Example MyTasks2 - Task 1'); this.$setTaskHelpDocs('task2', 'ES7 Decorator Example MyTasks2 - Task 2'); } task1 () { this.logger.log('MyTasks2 task1'); } task2 () { this.logger.log('MyTasks2 task2'); } } bz.add(MyTasks2); let p = bz.run('MyTasks', 'MyTasks.task2', 'MyTasks2', 'MyTasks2.task2'); // prints help results // bz.printHelp(); /* Output: MyTasks task1 MyTasks task2 MyTasks2 task1 MyTasks2 task2 */ */ $printHelp () { _.forEach(this.$getSubTasks(), (task) => { task.$printHelp(); }); if (this.$helpDocs) { this.beelzebub.drawBox(this.name); _.forEach(this.$helpDocs, (doc, taskName) => { // use helpLogger so time stamp's are not printed this.helpLogger.log(chalk.bold.underline(taskName)); this.helpLogger.log('\t', doc, '\n'); }); } } /** * Use this Task as root task * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyRootLevel extends Beelzebub.Tasks { constructor (config) { super(config); this.$useAsRoot(); } task1 () { this.logger.log('MyRootLevel task1'); } task2 () { this.logger.log('MyRootLevel task2'); } } class MyTasks1 extends Beelzebub.Tasks { default () { this.logger.log('MyTasks1 default'); } } class MyTasks2 extends Beelzebub.Tasks { constructor (config) { super(config); this.$setDefault('myDefault'); } myDefault () { this.logger.log('MyTasks2 myDefault'); } } bz.add(MyRootLevel); bz.add(MyTasks1); bz.add(MyTasks2); let p = bz.run( 'task1', 'task2', 'MyTasks1', 'MyTasks1.default', 'MyTasks2', 'MyTasks2.myDefault' ); /* Output: MyRootLevel task1 MyRootLevel task2 MyTasks1 default MyTasks1 default MyTasks2 myDefault MyTasks2 myDefault */ */ $useAsRoot () { this._rootLevel = true; this.name = '$root$'; } /** * Set a Task as default * @param {string} taskFuncName - This Class (Task) function name * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyRootLevel extends Beelzebub.Tasks { constructor (config) { super(config); this.$useAsRoot(); } task1 () { this.logger.log('MyRootLevel task1'); } task2 () { this.logger.log('MyRootLevel task2'); } } class MyTasks1 extends Beelzebub.Tasks { default () { this.logger.log('MyTasks1 default'); } } class MyTasks2 extends Beelzebub.Tasks { constructor (config) { super(config); this.$setDefault('myDefault'); } myDefault () { this.logger.log('MyTasks2 myDefault'); } } bz.add(MyRootLevel); bz.add(MyTasks1); bz.add(MyTasks2); let p = bz.run( 'task1', 'task2', 'MyTasks1', 'MyTasks1.default', 'MyTasks2', 'MyTasks2.myDefault' ); /* Output: MyRootLevel task1 MyRootLevel task2 MyTasks1 default MyTasks1 default MyTasks2 myDefault MyTasks2 myDefault */ */ $setDefault (taskFuncName) { this._defaultTaskFuncName = taskFuncName; } /** * Has any of the tasks ran before? * @returns {boolean} */ $hasRunBefore () { return this._beforeAllRun; } /** * Is this task root level? * @returns {boolean} */ $isRoot () { return this._rootLevel; } /** * Set name of Task Group/Class, when refering to Task Group in CLI or other Tasks * @param {string} name - New name of Task Group/Class */ $setName (name) { this.name = name; } /** * Get name of Task Group/Class * @returns {string} */ $getName () { return this.name; } /** * Get Task by Name * @param {string} name - Name of Task to get * @returns {function} */ $getTask (name) { return this._tasks[name]; } /** * Does this Task Group have a Task with the name? * @param {string} name - Name of Task * @returns {boolean} */ $hasTask (name) { return this._tasks.hasOwnProperty(name); } /** * Get SubTask by Name * @param {string} name - Name of Sub Task * @returns {object} */ $getSubTask (name) { return this._subTasks[name]; } /** * Set SubTask * @param {string} name - Name of Sub Task * @param {string} task - Task Class */ $setSubTask (name, task) { this._subTasks[name] = task; } /** * Does this have a Sub Task with the name? * @param {string} name - Name of Sub Task * @returns {boolean} */ $hasSubTask (name) { return this._subTasks.hasOwnProperty(name); } /** * Get All Sub Task(s) * @returns {object} */ $getSubTasks () { return this._subTasks; } /** * Set All Sub Task(s) * @param {object} tasks */ $setSubTasks (tasks) { this._subTasks = tasks; } /** * Set Global Vars * @param {object} vars */ $setGlobalVars (vars) { this.beelzebub.setGlobalVars(vars); } /** * Get Global Vars * @returns {object} */ $getGlobalVars () { return this.beelzebub.getGlobalVars(); } /** * Define Task Vars * @param {string} taskName - Name of Task * @param {object} taskDef - Var Defintion for Task * @example const Beelzebub = require('../../'); const bz = Beelzebub(options || { verbose: true }); const task = Beelzebub.TmplStrFunc.task; class MyTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$defineTaskVars('task1', { name: { type: 'String', default: 'hello' }, flag: { type: 'Boolean', default: true } }); this.$defineTaskVars('task2', { count: { type: 'Number', required: true }, verbose: { type: 'Boolean', alias: 'v', default: false } }); this.$defineTaskVars('task3', { fullname: { type: 'Object', properties: { first: { type: 'String' }, last: { type: 'String' } } }, list: { type: 'Array', items: { type: 'String' } } }); } task1 (customVars) { this.logger.log(`MyTasks task1 - ${JSON.stringify(this.$getGlobalVars())} ${customVars.name} ${customVars.flag}`); } task2 (customVars) { this.logger.log(`MyTasks task2 - ${customVars.count} ${customVars.verbose}`); } task3 (customVars) { this.logger.log(`MyTasks task3 - "${customVars.fullname.first} ${customVars.fullname.last}" ${customVars.list}`); } } bz.add(MyTasks); let p = bz.run( 'MyTasks.task1', task`MyTasks.task2:${{count: 100, verbose: true}}`, { task: 'MyTasks.task3', vars: { fullname: { first: 'hello', last: 'world' }, list: [ 'te', 'st' ] } } ); /* Output: MyTasks task1 - {} hello true MyTasks task2 - 100 true MyTasks task3 - "hello world" te,st */ */ $defineTaskVars (taskName, taskDef) { if (!_.isObject(this.$varDefs)) { this.$varDefs = {}; } this.$varDefs[taskName] = taskDef; } /** * Set Help Docs for Task * @param {string} taskName - Name of Task * @param {string} helpDocs - Help Docs for Task * @example const Beelzebub = require('../../'); const bz = Beelzebub(options || { verbose: true }); class MyTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$setDefault('task1'); this.$setTaskHelpDocs('task1', 'ES7 Decorator Example MyTasks - Task 1'); this.$setTaskHelpDocs('task2', 'ES7 Decorator Example MyTasks - Task 2'); } task1 () { this.logger.log('MyTasks task1'); } task2 () { this.logger.log('MyTasks task2'); } } bz.add(MyTasks); class MyTasks2 extends bz.Tasks { constructor (config) { super(config); this.$setDefault('task1'); this.$setTaskHelpDocs('task1', 'ES7 Decorator Example MyTasks2 - Task 1'); this.$setTaskHelpDocs('task2', 'ES7 Decorator Example MyTasks2 - Task 2'); } task1 () { this.logger.log('MyTasks2 task1'); } task2 () { this.logger.log('MyTasks2 task2'); } } bz.add(MyTasks2); let p = bz.run('MyTasks', 'MyTasks.task2', 'MyTasks2', 'MyTasks2.task2'); // prints help results // bz.printHelp(); /* Output: MyTasks task1 MyTasks task2 MyTasks2 task1 MyTasks2 task2 */ */ $setTaskHelpDocs (taskName, helpDocs) { if (!_.isObject(this.$helpDocs)) { this.$helpDocs = {}; } this.$helpDocs[taskName] = helpDocs; } /** * Get Define Task Vars by Name * @param {string} taskStr - Name of Task * @returns {object} Varaible Definition for Task */ $getVarDefsForTaskName (taskStr) { let taskParts = taskStr.split('.'); let taskName = taskParts.shift(); if (!taskName || !taskName.length) { taskName = 'default'; } // this.vLogger.log('taskName:', taskName); // this.vLogger.log('taskParts:', taskParts); if (this.$hasSubTask(taskName)) { let newTaskName = taskParts.join('.'); // this.vLogger.log('newTaskName:', newTaskName); return this.$getSubTask(taskName).$getVarDefsForTaskName(newTaskName); } else if (this.$hasTask(taskName)) { if (!this.$varDefs || _.keys(this.$varDefs).length === 0) { return null; } return this.$varDefs[taskName]; } } /** * This needs to be Extented * @interface * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyBaseTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$setName(config.name || 'MyBaseTasks'); this.value = config.value; this._delayTime = 300; } $init () { return this._delay('MyBaseTasks init'); } _delay (message) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, this._delayTime); }); } task1 () { return this._delay('MyBaseTasks task1 - ' + this.value); } } class MyTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$setName('MyTasks'); } $init () { this.logger.log('MyTasks init'); // simlate tasks dynamiclly added after some async event return new Promise((resolve, reject) => { setTimeout(() => { this.$addSubTasks(MyBaseTasks, { name: 'MyBaseTasks1', value: 123 }); this.$addSubTasks(MyBaseTasks, { name: 'MyBaseTasks2', value: 456 }); // done resolve(1234); }, 200); }); } task1 () { this.logger.log('MyTasks task1'); return this.$sequence('MyTasks.MyBaseTasks1.task1', 'MyTasks.MyBaseTasks2.task1'); } } bz.add(MyTasks); let p = bz.run('MyTasks.task1'); /* Output: MyTasks init MyBaseTasks init MyBaseTasks init MyTasks task1 MyBaseTasks task1 - 123 MyBaseTasks task1 - 456 */ */ $init () { return null; } /** * This needs to be Extented * @interface * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyTasks extends Beelzebub.Tasks { $beforeEach (taskInfo) { this.logger.log(`MyTasks beforeEach - ${taskInfo.task}`); } $afterEach (taskInfo) { this.logger.log(`MyTasks afterEach - ${taskInfo.task}`); } $beforeAll () { this.logger.log(`MyTasks beforeAll`); } $afterAll () { this.logger.log(`MyTasks afterAll`); } // my tasks task1 () { this.logger.log('MyTasks task1'); } task2 () { this.logger.log('MyTasks task2'); } } bz.add(MyTasks); let p = bz.run('MyTasks.task1', 'MyTasks.task2'); /* Output: MyTasks beforeAll MyTasks beforeEach - task1 MyTasks task1 MyTasks afterEach - task1 MyTasks beforeEach - task2 MyTasks task2 MyTasks afterEach - task2 MyTasks afterAll */ */ $beforeEach () { return null; } /** * This needs to be Extented * @interface * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyTasks extends Beelzebub.Tasks { $beforeEach (taskInfo) { this.logger.log(`MyTasks beforeEach - ${taskInfo.task}`); } $afterEach (taskInfo) { this.logger.log(`MyTasks afterEach - ${taskInfo.task}`); } $beforeAll () { this.logger.log(`MyTasks beforeAll`); } $afterAll () { this.logger.log(`MyTasks afterAll`); } // my tasks task1 () { this.logger.log('MyTasks task1'); } task2 () { this.logger.log('MyTasks task2'); } } bz.add(MyTasks); let p = bz.run('MyTasks.task1', 'MyTasks.task2'); /* Output: MyTasks beforeAll MyTasks beforeEach - task1 MyTasks task1 MyTasks afterEach - task1 MyTasks beforeEach - task2 MyTasks task2 MyTasks afterEach - task2 MyTasks afterAll */ */ $afterEach () { return null; } /** * This needs to be Extented * @interface * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MySubBaseTasks1 extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MySubBaseTasks1 init'); } // generator function * $beforeAll () { yield this._delay('MySubBaseTasks1 beforeAll'); } // generator function * $afterAll () { return this._delay('MySubBaseTasks1 afterAll'); } // generator function * $beforeEach (taskInfo) { return this._delay(`MySubBaseTasks1 beforeEach - ${taskInfo.task}`); } // generator function * $afterEach (taskInfo) { yield this._delay(`MySubBaseTasks1 afterEach - ${taskInfo.task}`); } taskA1 () { return this._delay('MySubBaseTasks1 taskA1'); } } class MySubBaseTasks2 extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MySubBaseTasks2 init'); } // generator function * $beforeAll () { yield this._delay('MySubBaseTasks2 beforeAll'); } // generator function * $afterAll () { return this._delay('MySubBaseTasks2 afterAll'); } // generator function * $beforeEach (taskInfo) { return this._delay(`MySubBaseTasks2 beforeEach - ${taskInfo.task}`); } // generator function * $afterEach (taskInfo) { yield this._delay(`MySubBaseTasks2 afterEach - ${taskInfo.task}`); } taskA2 () { return this._delay('MySubBaseTasks2 taskA2'); } } class MyBaseTasks extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MyBaseTasks init'); // see subtasksSimple or Advanced for details on adding sub tasks this.$addSubTasks(MySubBaseTasks1); } // returns promise $beforeAll () { return this._delay('MyBaseTasks beforeAll').then(() => { return this.$addSubTasks(MySubBaseTasks2); }); } // returns promise $afterAll () { return this._delay('MyBaseTasks afterAll'); } // returns promise $beforeEach (taskInfo) { return this._delay(`MyBaseTasks beforeEach - ${taskInfo.task} ${JSON.stringify(taskInfo.vars)}`); } // returns promise $afterEach (taskInfo) { return this._delay(`MyBaseTasks afterEach - ${taskInfo.task} ${JSON.stringify(taskInfo.vars)}`); } taskA (vars) { return this._delay(`MyBaseTasks taskA - ${vars.hello}`); } taskB (vars) { return this._delay('MyBaseTasks taskB'); } } class MyTasks extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MyTasks init'); // see subtasksSimple or Advanced for details on adding sub tasks this.$addSubTasks(MyBaseTasks); } // generator function * $beforeEach (taskInfo) { yield this._delay(`MyTasks beforeEach - ${taskInfo.task}`); } // generator function * $afterEach (taskInfo) { yield this._delay(`MyTasks afterEach - ${taskInfo.task}`); } // generator function * $beforeAll () { yield this._delay('MyTasks beforeAll'); } // generator function * $afterAll () { yield this._delay('MyTasks afterAll'); } task1 () { this.logger.log('MyTasks task1'); return this.$sequence({ task: '.MyBaseTasks.taskA', vars: { hello: 'world' } }, '.MyBaseTasks.taskB', '.MyBaseTasks.MySubBaseTasks2.taskA2'); } task2 () { this.logger.log('MyTasks task2'); } _internalFunction () { this.logger.error('this should be ignored'); } } bz.add(MyTasks); let p = bz.run('MyTasks.MyBaseTasks.MySubBaseTasks1.taskA1', 'MyTasks.task1', 'MyTasks.task2'); /* Output: MyTasks init MyBaseTasks init MySubBaseTasks1 init MyTasks beforeAll MyBaseTasks beforeAll MySubBaseTasks2 init MySubBaseTasks1 beforeAll MySubBaseTasks1 beforeEach - taskA1 MySubBaseTasks1 taskA1 MySubBaseTasks1 afterEach - taskA1 MyTasks beforeEach - task1 MyTasks task1 MyBaseTasks beforeEach - taskA {"hello":"world"} MyBaseTasks taskA - world MyBaseTasks afterEach - taskA {"hello":"world"} MyBaseTasks beforeEach - taskB {} MyBaseTasks taskB MyBaseTasks afterEach - taskB {} MySubBaseTasks2 beforeAll MySubBaseTasks2 beforeEach - taskA2 MySubBaseTasks2 taskA2 MySubBaseTasks2 afterEach - taskA2 MyTasks afterEach - task1 MyTasks beforeEach - task2 MyTasks task2 MyTasks afterEach - task2 MySubBaseTasks1 afterAll MySubBaseTasks2 afterAll MyBaseTasks afterAll MyTasks afterAll */ */ $beforeAll () { return null; } /** * This needs to be Extented * @interface * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MySubBaseTasks1 extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MySubBaseTasks1 init'); } // generator function * $beforeAll () { yield this._delay('MySubBaseTasks1 beforeAll'); } // generator function * $afterAll () { return this._delay('MySubBaseTasks1 afterAll'); } // generator function * $beforeEach (taskInfo) { return this._delay(`MySubBaseTasks1 beforeEach - ${taskInfo.task}`); } // generator function * $afterEach (taskInfo) { yield this._delay(`MySubBaseTasks1 afterEach - ${taskInfo.task}`); } taskA1 () { return this._delay('MySubBaseTasks1 taskA1'); } } class MySubBaseTasks2 extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MySubBaseTasks2 init'); } // generator function * $beforeAll () { yield this._delay('MySubBaseTasks2 beforeAll'); } // generator function * $afterAll () { return this._delay('MySubBaseTasks2 afterAll'); } // generator function * $beforeEach (taskInfo) { return this._delay(`MySubBaseTasks2 beforeEach - ${taskInfo.task}`); } // generator function * $afterEach (taskInfo) { yield this._delay(`MySubBaseTasks2 afterEach - ${taskInfo.task}`); } taskA2 () { return this._delay('MySubBaseTasks2 taskA2'); } } class MyBaseTasks extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MyBaseTasks init'); // see subtasksSimple or Advanced for details on adding sub tasks this.$addSubTasks(MySubBaseTasks1); } // returns promise $beforeAll () { return this._delay('MyBaseTasks beforeAll').then(() => { return this.$addSubTasks(MySubBaseTasks2); }); } // returns promise $afterAll () { return this._delay('MyBaseTasks afterAll'); } // returns promise $beforeEach (taskInfo) { return this._delay(`MyBaseTasks beforeEach - ${taskInfo.task} ${JSON.stringify(taskInfo.vars)}`); } // returns promise $afterEach (taskInfo) { return this._delay(`MyBaseTasks afterEach - ${taskInfo.task} ${JSON.stringify(taskInfo.vars)}`); } taskA (vars) { return this._delay(`MyBaseTasks taskA - ${vars.hello}`); } taskB (vars) { return this._delay('MyBaseTasks taskB'); } } class MyTasks extends Beelzebub.Tasks { _delay (message, delay = 100) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } $init () { this.logger.log('MyTasks init'); // see subtasksSimple or Advanced for details on adding sub tasks this.$addSubTasks(MyBaseTasks); } // generator function * $beforeEach (taskInfo) { yield this._delay(`MyTasks beforeEach - ${taskInfo.task}`); } // generator function * $afterEach (taskInfo) { yield this._delay(`MyTasks afterEach - ${taskInfo.task}`); } // generator function * $beforeAll () { yield this._delay('MyTasks beforeAll'); } // generator function * $afterAll () { yield this._delay('MyTasks afterAll'); } task1 () { this.logger.log('MyTasks task1'); return this.$sequence({ task: '.MyBaseTasks.taskA', vars: { hello: 'world' } }, '.MyBaseTasks.taskB', '.MyBaseTasks.MySubBaseTasks2.taskA2'); } task2 () { this.logger.log('MyTasks task2'); } _internalFunction () { this.logger.error('this should be ignored'); } } bz.add(MyTasks); let p = bz.run('MyTasks.MyBaseTasks.MySubBaseTasks1.taskA1', 'MyTasks.task1', 'MyTasks.task2'); /* Output: MyTasks init MyBaseTasks init MySubBaseTasks1 init MyTasks beforeAll MyBaseTasks beforeAll MySubBaseTasks2 init MySubBaseTasks1 beforeAll MySubBaseTasks1 beforeEach - taskA1 MySubBaseTasks1 taskA1 MySubBaseTasks1 afterEach - taskA1 MyTasks beforeEach - task1 MyTasks task1 MyBaseTasks beforeEach - taskA {"hello":"world"} MyBaseTasks taskA - world MyBaseTasks afterEach - taskA {"hello":"world"} MyBaseTasks beforeEach - taskB {} MyBaseTasks taskB MyBaseTasks afterEach - taskB {} MySubBaseTasks2 beforeAll MySubBaseTasks2 beforeEach - taskA2 MySubBaseTasks2 taskA2 MySubBaseTasks2 afterEach - taskA2 MyTasks afterEach - task1 MyTasks beforeEach - task2 MyTasks task2 MyTasks afterEach - task2 MySubBaseTasks1 afterAll MySubBaseTasks2 afterAll MyBaseTasks afterAll MyTasks afterAll */ */ $afterAll () { return null; } /** * Is Task Running? * @returns {boolean} */ $getRunning () { return this._running; } /** * Register the Task with BZ * @returns {object} Promise */ $register () { // this.vLogger.log('$register start'); let tList = []; this._bfsTaskBuilder(tList, this); // run init, running as optimal to shortcut $init's that don't return promises let initPromise = this._normalizeExecFuncToPromise(this.$init, this); // this.vLogger.log('$register bfsTaskBuilder outList:', tList); this._addTasks(tList, this); return initPromise // .then((results) => { // this.vLogger.log('$register initFunctionList done:', results); // }) .then((results) => { // this.vLogger.log('$register initFunctionList done:', results); return results; }); } /** * Util - Start Task Stats * @private */ _taskStatsStart (parent, taskName) { let name = taskName; if (parent.name !== '$root$') { name = `${this.namePath}.${taskName}`; } this.logger.group(name); return this._stats.startTask(); } /** * Util - End Task Stats * @private */ _taskStatsEnd (parent, taskName, statsId) { let name = taskName; if (parent.name !== '$root$') { name = `${this.namePath}.${taskName}`; } this._stats.endTask(statsId); // TODO: add option to not display this const stats = this._stats.getTask(statsId); const time = Number(stats.diff.time.toFixed(2)); this.logger.groupEnd(`${name} (${time} ms)`); } /** * Util - Run Before All * @private */ _runBeforeAll () { return this._normalizeExecFuncToPromise(this.$beforeAll, this) .then(() => { this._beforeAllRun = true; }); } /** * Util - Run After All * @private */ _runAfterAll () { // sequance running all sub task AfterAll function let afterPromises = []; _.forEach(this.$getSubTasks(), (task) => { afterPromises.push(() => { return task._runAfterAll(); }); }); return whenSeq(afterPromises).then(() => { return this._normalizeExecFuncToPromise(this.$afterAll, this); }); } /** * Util - Add Tasks * @private */ _addTasks (tList, task) { // this.vLogger.log('addTasksToGulp tList:', tList, ', name:', this.name, ', rootLevel:', this._rootLevel, ', this != task:', this != task); _.forEach(tList, (funcName) => { let taskId = ''; if ((this !== task) &amp;&amp; !this._rootLevel) { taskId += task.name + '.'; } taskId += funcName; if (funcName === this._defaultTaskFuncName &amp;&amp; funcName !== 'default') { // default tasks have two entries this._tasks[taskId] = { taskId: taskId, tasksObj: task, func: task[funcName] }; taskId = 'default'; } // this.vLogger.log('taskId:', taskId); this._tasks[taskId] = { taskId: taskId, tasksObj: task, func: task[funcName] }; }); } // TODO: ??? combine the logic of 'add' and 'addSubTasks' // move to recursive run model using task $register instead of mixing sub tasks with current task class // TODO: should this always return a promise? when adding in $init should the use be forced to wait on this? /** * Add Sub Tasks * @public * @param {object} Task - Task Class * @param {object} [config={}] - Config for Task * @returns {object} Promise * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyBaseTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$setName(config.name || 'MyBaseTasks'); this.value = config.value; this._delayTime = 300; } $init () { return this._delay('MyBaseTasks init'); } _delay (message) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, this._delayTime); }); } task1 () { return this._delay('MyBaseTasks task1 - ' + this.value); } } class MyTasks extends Beelzebub.Tasks { constructor (config) { super(config); this.$setName('MyTasks'); } $init () { this.logger.log('MyTasks init'); // simlate tasks dynamiclly added after some async event return new Promise((resolve, reject) => { setTimeout(() => { this.$addSubTasks(MyBaseTasks, { name: 'MyBaseTasks1', value: 123 }); this.$addSubTasks(MyBaseTasks, { name: 'MyBaseTasks2', value: 456 }); // done resolve(1234); }, 200); }); } task1 () { this.logger.log('MyTasks task1'); return this.$sequence('MyTasks.MyBaseTasks1.task1', 'MyTasks.MyBaseTasks2.task1'); } } bz.add(MyTasks); let p = bz.run('MyTasks.task1'); /* Output: MyTasks init MyBaseTasks init MyBaseTasks init MyTasks task1 MyBaseTasks task1 - 123 MyBaseTasks task1 - 456 */ */ $addSubTasks (Task, config = {}) { let task = null; if (_.isFunction(Task) &amp;&amp; _.isObject(Task)) { config.parentPath = this.namePath; config.beelzebub = this.beelzebub; task = new Task(config); } else { task = Task; } if (!this.beelzebub.isLoading()) { return task.$register().then(() => { this.$setSubTask(task.$getName(), task); }); // this.vLogger.error('$addSubTasks can only be called during init'); // return when.reject(); } else { // this.vLogger.log('$addSubTasks addInitFunction', task.name); this.beelzebub.addInitFunction(() => { return task.$register(); }); // this.vLogger.log('task:', task); this.$setSubTask(task.$getName(), task); return when.resolve(); } } /** * Util - Normalize Execute of Function, Promise, or Generator (using CO) * @private */ _normalizeExecFuncToPromise (func, parent, ...args) { let p = null; // this.logger.log('normalizeExecFuncToPromise', // 'isPromise:', isPromise(func), // ', isGenerator:', isGenerator(func), // ', isFunction:', _.isFunction(func)); // func already a promise if (util.isPromise(func)) { p = func; } // func is a generator function else if (util.isGenerator(func)) { // run generator using co if (parent) { p = co(func.bind(parent, ...args)); } else { p = co(func.bind(func, ...args)); } } // if task is function, run it else if (_.isFunction(func)) { if (parent) { p = func.apply(parent, args); } else { p = func(...args); } } else { // TODO: check other this.logger.warn('other type?? func:', func, ', parent:', parent); } // this.logger.log('normalizeExecFuncToPromise', // 'isStream:', isStream(p), // ', isPromise:', isPromise(p), // ', optimize:', optimize); // convert streams to promise if (util.isStream(p)) { p = streamToPromise(p); } if (!util.isPromise(p)) { p = when.resolve(p); } return p; } /** * Util - Breath First Search Task Builder * @private */ _bfsTaskBuilder (outList, task, name) { let proto = Object.getPrototypeOf(task); // this.vLogger.log('task:', task, ', proto:', proto, ', name:', name); if (proto &amp;&amp; _.isObject(proto)) { // this.vLogger.log('name:', name, ', task.name:', task.name); name = name || task.name; let oproto = this._bfsTaskBuilder(outList, proto, name); if (Object.getPrototypeOf(oproto) &amp;&amp; !(oproto === BzTasks.prototype)) { // this.vLogger.log('name:', name, 'oproto:', oproto, ', oproto instanceof BzTasks:', (oproto === BzTasks.prototype)); let tList = Object.getOwnPropertyNames(oproto); tList = tList.filter((p) => { return ( _.isFunction(task[p]) &amp;&amp; (p !== 'constructor') /* NOT constructor */ &amp;&amp; (p[0] !== '_') /* doesn't start with underscore */ &amp;&amp; (p[0] !== '$') /* doesn't start with $ */ ); }); // this.vLogger.log('name:', name, ', oproto:', oproto, ', tList:', tList); for (let i = 0; i &lt; tList.length; i++) { outList.push(tList[i]); } } } return task; } /** * Runs task(s) in sequence * @param {(object|string)} args - task(s) * @returns {Object} Promise * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyTasks extends Beelzebub.Tasks { _delay (message, delay = 300) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } task1 () { this.logger.log('MyTasks task1'); return this.$sequence('.task3', '.task4'); } task2 () { return this._delay('MyTasks task2', 200); } task3 () { this.logger.log('MyTasks task3'); return this.$run('.task5', '.task6'); } task4 () { return this._delay('MyTasks task4', 400); } task5 () { this.logger.log('MyTasks task5'); } task6 () { return this._delay('MyTasks task6', 600); } } bz.add(MyTasks); // params are run in sequence let p = bz.run('MyTasks.task1', 'MyTasks.task2'); /* Output: MyTasks task1 MyTasks task3 MyTasks task5 MyTasks task6 MyTasks task4 MyTasks task2 */ */ $sequence (...args) { // TODO: prevent infinite loop return this.beelzebub.sequence(this, ...args); } /** * Runs task(s) in parallel * @param {(function|string)} args - task(s) * @returns {object} Promise * @example let Beelzebub = require('../../'); let bz = Beelzebub(options || { verbose: true }); class MyTasks extends Beelzebub.Tasks { _delay (message, delay = 300) { return new Promise((resolve, reject) => { setTimeout(() => { this.logger.log(message); resolve(); }, delay); }); } task1 () { this.logger.log('MyTasks task1'); return this.$parallel('.task3', '.task4'); } task2 () { return this._delay('MyTasks task2', 200); } task3 () { this.logger.log('MyTasks task3'); return this.$run(['.task5', '.task6']); } task4 () { return this._delay('MyTasks task4', 400); } task5 () { this.logger.log('MyTasks task5'); } task6 () { return this._delay('MyTasks task6', 600); } } bz.add(MyTasks); // arrays are run in parallel let p = bz.run(['MyTasks.task1', 'MyTasks.task2']); /* Output: MyTasks task1 MyTasks task3 MyTasks task5 MyTasks task2 MyTasks task4 MyTasks task6 */ */ $parallel (...args) { // TODO: prevent infinite loop return this.beelzebub.parallel(this, ...args); } /** * Runs task(s) - multi args run in sequence, arrays are run in parallel * @param {(function|string)} args - task(s) * @returns {object} Promise */ $run (...args) { // TODO: prevent infinite loop return this.beelzebub.run(this, ...args); } /** * Util - Breath First Search Task Builder * @private */ _waitForInit () { let p = null; // wait for self to complete if (this.beelzebub.isLoading()) { p = this.beelzebub.getInitPromise(); } else { p = when.resolve(); } return p; } /** * Util - Run task(s) in sequence * @private * @param {(function|string)} [parent] - Parent Task * @param {(object|string)} [args] - Arguments for Task * @returns {object} Promise */ _sequence (parent, ...args) { // this.vLogger.log('sequence args:', args); // this.vLogger.log('sequence parent:', parent); if (parent &amp;&amp; (_.isString(parent) || _.isArray(parent) || util.isTaskObject(parent))) { args.unshift(parent); parent = undefined; // this.vLogger.log('sequence args:', args); } return this._waitForInit().then(() => { // normalize tasks (aka args) args = this._normalizeTask(parent, args); let aTasks = []; _.forEach(args, (task) => { aTasks.push(() => { return this._runPromiseTask(parent, task); }); }); // this.vLogger.log('sequence args:', aTasks); return whenSeq(aTasks); }); } /** * Util - Runs task(s) in parallel * @private * @param {(function|string)} [parent] - Parent Task * @param {(object|string)} [args] - Arguments for Task * @returns {object} Promise */ _parallel (parent, ...args) { // this.vLogger.log('parallel args:', args); if (parent &amp;&amp; (_.isString(parent) || _.isArray(parent) || util.isTaskObject(parent))) { args.unshift(parent); parent = undefined; // this.vLogger.log('parallel args:', args); } return this._waitForInit().then(() => { // normalize tasks (aka args) args = this._normalizeTask(parent, args); let pList = _.map(args, (task) => { return this._runPromiseTask(parent, task); }); // this.vLogger.log('parallel pList:', pList); return when.all(pList); }); } /** * Util - Runs task(s) - multi args run in sequence, arrays are run in parallel * @private * @param {(function|string)} [parent] - Parent Task * @param {(object|string)} [args] - Arguments for Task * @returns {object} Promise */ _run (parent, ...args) { // this.vLogger.log('run args:', args); // if no args if (_.isArray(args) &amp;&amp; args.length === 0) { // check if root, level and has 'default' task if (this._rootLevel &amp;&amp; _.isObject(this._tasks[this._defaultTaskFuncName])) { args.push(this._defaultTaskFuncName); } } if (parent &amp;&amp; (_.isString(parent) || _.isArray(parent) || util.isTaskObject(parent))) { args.unshift(parent); parent = undefined; // this.vLogger.log('run args:', args); } return this._waitForInit().then(() => { let promise = null; // normalize tasks (aka args) args = this._normalizeTask(parent, args); if (args.length === 1) { promise = this._runPromiseTask(parent, args[0]); } else { // multi args mean, run in sequence promise = this._sequence(parent, ...args); } this._running = promise.then((result) => { this._running = null; return result; }); return this._running.catch((e) => { this.logger.error(e); }); }); } /** * Util - Run a task * @private * @param {string} task * @returns {object} Promise */ _runTask (task) { // this.vLogger.log('task class:', this.name // , ', running task:', task // , ', all tasks:', _.keys(this._tasks) // , ', all subTasks:', _.keys(this.$getSubTasks())); // if no task specified, then use default if (!task) { task = { task: 'default' }; // console.error('setting to default'); } let taskParts = task.task.split('.'); let taskName = taskParts.shift(); if (!taskName || !taskName.length) { taskName = 'default'; } if (this.$hasSubTask(taskName)) { task.task = taskParts.join('.'); // this.vLogger.info('runTask taskName:', taskName, ', runTask:', task.task); let tastObject = this.$getSubTask(taskName); // has beforeAll run if (!tastObject.$hasRunBefore()) { // call run beforeAll function which sets internal var if ran before // not crazy about using private, but don't want people to thing it's ok to run this return tastObject._runBeforeAll().then(() => { return tastObject._runTask(task); }); } // beforeAll has already run else { return tastObject._runTask(task); } } else if (this.$hasTask(taskName)) { const taskObj = this.$getTask(taskName); return this._execTaskFun(taskName, taskObj.func, taskObj.tasksObj, task.vars); } else { this.logger.error(`Task "${taskName}" - not found`); } } /** * Util - Run Before Each * @private * @param {object} parent * @param {object} taskInfo * @returns {object} Promise */ _runBeforeEach (parent, taskInfo) { // run beforeEach return this._normalizeExecFuncToPromise(parent.$beforeEach, parent, taskInfo); } /** * Util - Exec Task Fucntion * @private * @param {string} taskName * @param {function} func * @param {object} parent * @param {object} vars * @returns {object} Promise */ _execTaskFun (taskName, func, parent, vars) { const taskInfo = { task: taskName, vars: vars }; let fullTaskName = taskName; if (parent &amp;&amp; _.isString(parent.name) &amp;&amp; parent.name !== '$root$') { fullTaskName = `${this.namePath}.${taskName}`; } // this.vLogger.info('execTaskFun taskName:', JSON.stringify(taskInfo)); let beforePromise = null; if (!parent.$hasRunBefore()) { // call run beforeAll function which sets internal var if ran before // not crazy about using private, but don't want people to thing it's ok to run this beforePromise = parent._runBeforeAll().then(() => { return this._runBeforeEach(parent, taskInfo); }); } else { beforePromise = this._runBeforeEach(parent, taskInfo); } // run beforeAll let statsId = null; return beforePromise.then(() => { // after, before this.beelzebub.emit('$before', { task: fullTaskName, vars: taskInfo.vars }); statsId = parent._taskStatsStart(parent, taskName); // if parent is Object if (_.isObject(parent)) { // add context aware $emit parent.$emit = (name, data) => { this.beelzebub.emit(name, { task: fullTaskName, vars: taskInfo.vars }, data); }; } // run task function return this._normalizeExecFuncToPromise(func, parent, vars); }) .then(() => { parent._taskStatsEnd(parent, taskName, statsId); // run afterEach return this._normalizeExecFuncToPromise(parent.$afterEach, parent, taskInfo); }) .then(() => { // after, after this.beelzebub.emit('$after', { task: fullTaskName, vars: taskInfo.vars }); }); } /** * Util - Runs Promise task * TODO: issues, need to circle back around to top level BZ and trickle down * TODO: can we merge this with runTask? * so it can recursivly chain down to resolve promises all the way down * @private * @param {object} parent