'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 && 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) && !this._rootLevel) {
taskId += task.name + '.';
}
taskId += funcName;
if (funcName === this._defaultTaskFuncName &&
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) && _.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 && _.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) && !(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]) &&
(p !== 'constructor') /* NOT constructor */ &&
(p[0] !== '_') /* doesn't start with underscore */ &&
(p[0] !== '$') /* doesn't start with $ */
);
});
// this.vLogger.log('name:', name, ', oproto:', oproto, ', tList:', tList);
for (let i = 0; i < 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 && (_.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 && (_.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) && args.length === 0) {
// check if root, level and has 'default' task
if (this._rootLevel && _.isObject(this._tasks[this._defaultTaskFuncName])) {
args.push(this._defaultTaskFuncName);
}
}
if (parent && (_.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 && _.isString(parent.name) && 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 - Parent Task
* @param {(function|object)} task
* @returns {object} Promise
*/
_runPromiseTask (parent, task) {
let p = null;
// if task is array, then run in parallel
if (_.isArray(task)) {
return this._parallel(parent, ...task);
}
// if task is object, then find function and parent in list
else if (_.isObject(task)) {
let taskParts = [];
let taskName = 'default';
// task is function
if (_.isFunction(task.task)) {
p = this._execTaskFun(taskName, task.task, parent, task.vars);
}
// task is string
else if (_.isString(task.task)) {
taskParts = task.task.split('.');
taskName = taskParts.shift();
if (!this.$hasTask(taskName)) {
// now check if in sub level
if (this.$hasSubTask(taskName)) {
task.task = taskParts.join('.');
// this.vLogger.info('runPromiseTask taskName:', taskName, ', runTask:', task.task);
let tastObject = this.$getSubTask(taskName);
// run parent beforeAll running subTask
// 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
p = tastObject._runBeforeAll().then(() => {
return tastObject._runTask(task);
});
}
// beforeAll has already run
else {
p = tastObject._runTask(task);
}
} else {
let error = `task name not found: "${task.task}"`;
this.logger.error(error);
p = when.reject(error);
}
}
}
else {
let error = `invalid task name: "${task.task}"`;
this.logger.error(error);
p = when.reject(error);
}
// no promise
if (!p) {
if (taskParts.length > 0) {
task.task = taskParts.join('.');
// this.vLogger.info('runPromiseTask taskName:', taskName, ', runTask:', task.task);
p = this._tasks[taskName]._runTask(task);
} else {
if (this._tasks[taskName]) {
task = this._tasks[taskName].func;
parent = this._tasks[taskName].tasksObj;
p = this._execTaskFun(taskName, task, parent, task.vars);
} else {
let error = `task name not found: "${task}"`;
this.logger.error(error);
p = when.reject(error);
}
}
}
}
else {
let error = `task type not supported: "${task}"`;
this.logger.trace(error);
p = when.reject(error);
}
// TODO: what happens to the data at the end? TBD
return p;
}
/**
* Util - Normalize Task
* @private
* @param {object} parent - Parent Task
* @param {(function|object)} task
* @returns {object} Promise
*/
_normalizeTask (parent, tasks) {
let objTasks = _.map(tasks, (task) => {
let taskObj;
if (_.isString(task)) {
// if first char "." then relative to parent path
if (task.charAt(0) === '.') {
if (!parent) {
this.logger.trace('parent missing but expected');
} else {
task = parent.namePath + task;
}
}
let taskParts = task.split('.');
let taskName = taskParts.shift();
let taskFullName = task;
let taskVarParts = task.split(':');
let taskVars = {};
if (taskVarParts.length > 0) {
taskFullName = taskVarParts.shift();
taskVars = taskVarParts.join(':');
// vars a string and empty, this can happen is no vars pass to task string
if (_.isString(taskVars) && taskVars.length === 0) {
taskVars = {};
} else {
try {
taskVars = JSON.parse(taskVars);
}
catch (err) {
// this is ok
this.vLogger.warn('Parsing Task Error:', err);
}
}
}
if (!this.$getSubTask(taskName) && !this.$getTask(taskName)) {
this.logger.warn(taskFullName, 'task not added');
return false;
}
taskObj = {
task: taskFullName,
vars: taskVars
};
}
else if (_.isArray(task)) {
taskObj = this._normalizeTask(parent, task);
}
else if (_.isFunction(task)) {
taskObj = {
task: task
};
}
else if (_.isObject(task)) {
if (!task.hasOwnProperty('task')) {
this.logger.warn('invalid object: task property required');
return null;
}
// if first char "." then relative to parent path
if (task.task.charAt(0) === '.') {
if (!parent) {
this.logger.trace('parent missing but expected');
} else {
task.task = parent.namePath + task.task;
}
}
return task;
} else {
this.logger.warn('unknown task input type');
return null;
}
// make sure vars is object
if (taskObj.vars === null || taskObj.vars === undefined) {
taskObj.vars = {};
}
if (!_.isObject(taskObj.vars)) {
this.logger.warn('Vars should be an object');
taskObj.vars = {};
}
return taskObj;
});
// apply var definitions if they exist
// definitions -> defaults, data types and requirements
objTasks = this._applyVarDefsToAllTasks(objTasks);
// this.vLogger.log('objTasks:', objTasks);
return objTasks;
}
/**
* Util - Apply Variable Definitions To All Tasks
* @private
* @param {object} objTasks - Tasks
* @returns {object} objTasks
*/
_applyVarDefsToAllTasks (objTasks) {
objTasks = _.map(objTasks, (task) => {
return this._applyVarDefToTask(task);
});
return objTasks;
}
/**
* Util - Apply Variable Definitions To Task
* @private
* @param {object} task - Task
* @returns {object} task
*/
_applyVarDefToTask (task) {
if (_.isArray(task)) {
return _.map(task, (t) => {
return this._applyVarDefToTask(t);
});
}
// task object and task is string
else if (_.isObject(task) && _.isString(task.task)) {
let taskParts = task.task.split('.');
let taskName = taskParts.shift();
if (!taskName || !taskName.length) {
taskName = 'default';
}
if (this.$hasSubTask(taskName)) {
// use copy of tasks to pass to child, as we don't want to mutate the task name
let newTask = _.cloneDeep(task);
newTask.task = taskParts.join('.');
newTask = this.$getSubTask(taskName)._applyVarDefToTask(newTask);
// update task vars
task.vars = newTask.vars;
}
else if (this.$hasTask(taskName)) {
// no vardefs or no keys in object
if (!this.$varDefs || _.keys(this.$varDefs).length === 0) {
return task;
}
// has vars definition
if (this.$varDefs[taskName]) {
// apply def to vars
task.vars = this._applyVarDefs(this.$varDefs[taskName], task.vars);
}
} else {
// this.logger.error(`Task "${taskName}" - not found`);
}
return task;
}
// could be task is function or something else, but can't look up vardef to apply
else {
return task;
}
}
/**
* Util - Apply Variable Definitions to input Variables
* TODO: need loads of tests to cover all the conditionals
* @private
* @param {object} varDefs - variable defintions
* @param {object} vars - input variable
* @returns {object} vars
*/
_applyVarDefs (varDefs, vars) {
// this.vLogger.info('varDefs:', varDefs);
// this.vLogger.info('vars:', vars);
_.forEach(varDefs, (varDef, key) => {
let type = varDef.type.toLowerCase();
// if as alias
if (varDef.alias) {
let tkey = varDef.alias;
// if alias var has value, then use this as the key
if (vars[tkey] !== null && vars[tkey] !== undefined) {
vars[key] = vars[tkey];
}
}
// var is set to something
if (vars[key] !== null && vars[key] !== undefined) {
if (type === 'string') {
// not string
if (!_.isString(vars[key])) {
this.logger.error(`${key} is not a string but defined as one, converting to string`);
vars[key] = String(vars[key]);
}
}
else if (type === 'number') {
// not number
if (!_.isNumber(vars[key])) {
this.logger.error(`${key} is not a number but defined as one, converting to number`);
vars[key] = Number(vars[key]);
}
}
else if (type === 'boolean') {
// not boolean
if (!_.isBoolean(vars[key])) {
this.logger.error(`${key} is not a boolean but defined as one, converting to boolean`);
// is string, only compare if 'true', otherwise false
if (_.isString(vars[key])) {
vars[key] = (vars[key].toLowerCase() === 'true');
}
// convert all else use Boolean
else {
vars[key] = Boolean(vars[key]);
}
}
}
else if (type === 'array') {
// not array
if (!_.isArray(vars[key])) {
this.logger.error(`${key} is not a array but defined as one, converting to array`);
if (_.isString(vars[key])) {
try {
vars[key] = JSON.parse(vars[key]);
}
catch (err) {
// if parsing fails then split the string by commas
vars[key] = vars[key].split(',');
}
}
else {
vars[key] = Array(vars[key]);
}
}
}
else if (type === 'object') {
if (!_.isObject(vars[key])) {
this.logger.error(`${key} is not a object but defined as one, converting to object`);
// convert vars[key] to object
if (_.isString(vars[key])) {
try {
vars[key] = JSON.parse(vars[key]);
}
catch (err) {
// if parsing fails then just stick in data prop
this.logger.error(`object "${key}" json parsing error: ${err}`);
vars[key] = { data: vars[key] };
}
}
else {
vars[key] = { data: vars[key] };
}
}
let varProps = varDef.properties;
if (!varProps || !_.isObject(varProps)) {
this.logger.error(`object "${key}" properties is not defined as object, skipping all sub properties.`);
} else {
// recursivly check children (properties)
vars[key] = this._applyVarDefs(varProps, vars[key]);
}
}
else {
this.logger.warn(`Unknown Variable Definition Type: ${type}`);
}
}
// not set to anything
else {
if (varDef.required) {
this.logger.error(`Var "${key}" is required but not set in vars.`);
}
let defValue = null;
if (type === 'string') { defValue = ''; }
else if (type === 'number') { defValue = 0; }
else if (type === 'boolean') { defValue = false; }
else if (type === 'array') { defValue = []; }
else if (type === 'object') { defValue = {}; }
// has default
if (varDef.default) {
vars[key] = varDef.default;
}
// else default to empty string
else {
vars[key] = defValue;
}
}
});
return vars;
}
}
module.exports = BzTasks;