async-chainable-progress
Version:
Plugin for async-chainable that adds progress bars, spinners and other widgets
349 lines (316 loc) • 8.32 kB
JavaScript
var _ = require('lodash');
var argy = require('argy');
var clui = require('clui');
var terminal = require('terminal-kit').terminal;
module.exports = function() {
this._progressObjects = {};
// Main renderer {{{
this._plugins['progress'] = function(params) {
var self = this;
// Set Default options {{
_.defaults(params, this._progressDefaults);
// }}}
// Check that the ID'd object exists {{{
var item = params.item;
if (!item.id || !item.type) throw new Error("No ID or type for progress item");
if (!this._progressObjects[item.id]) {
this._progressObjects[item.id] = {};
switch (item.type) {
case 'progressBar':
this._progressObjects[item.id] = _.defaults({
rendered: 0,
type: 'progressBar',
objRef: new clui.Progress(params.width || params.progress.width),
max: params.progress.max,
value: params.progress.value,
text: params.text || params.progress.text,
}, item);
break;
case 'spinner':
this._progressObjects[item.id] = _.defaults({
rendered: 0,
type: 'spinner',
spinners: params.spinners || params.spinner.spinners,
text: params.text || params.spinner.text,
}, item);
break;
default: throw new Error('Unknown async-chainable-progress type: ' + item.type);
}
}
// }}}
// Accept updated parameters {{{
['value', 'max', 'text'].forEach(function(option) {
if (item[option]) self._progressObjects[item.id][option] = item[option];
});
// }}}
this._progressRender();
this._execute();
};
this._progressRender = function() {
for (var id in this._progressObjects) {
var obj = this._progressObjects[id];
switch (obj.type) {
case 'progressBar':
if (obj.rendered++ > 0) terminal.up(1).eraseLineAfter();
process.stdout.write(
(obj.text ? obj.text + ' ' : '') +
obj.objRef.update(obj.value, obj.max) +
"\n"
);
break;
case 'spinner':
if (obj.rendered++ > 0) terminal.up(1).eraseLineAfter();
obj.rendered++;
process.stdout.write('\u001b[96m' + obj.spinners[obj.rendered % obj.spinners.length] + '\u001b[90m ' + obj.text + "\u001b[0m\n");
break;
}
}
};
// }}}
// .progress() {{{
this.progress = function() {
var chain = this;
argy(arguments)
.ifForm('', function() {
chain._struct.push({
type: 'progress',
item: {
id: 'anonBar',
type: 'progressBar',
value: 0,
},
});
})
.ifForm('string', function(key) {
chain._struct.push({
type: 'progress',
item: {
type: 'progressBar',
id: key,
},
});
})
.ifForm('string string', function(key, text) {
chain._struct.push({
type: 'progress',
item: {
type: 'progressBar',
id: key,
text: text,
},
});
})
.ifForm('null string', function(junk, text) {
chain._struct.push({
type: 'progress',
item: {
type: 'progressBar',
id: 'anonBar',
text: text,
},
});
})
.ifForm('string object', function(key, settings) {
settings.id = key;
settings.type = 'progressBar';
chain._struct.push({type: 'progress', item: sertings});
})
.ifForm('string number', function(key, progress) {
this._struct.push({
type: 'progress',
item: {
type: 'progressBar',
id: key,
value: progress,
},
});
})
.ifForm('string number number', function(key, progress, max) {
chain._struct.push({
type: 'progress',
item: {
type: 'progressBar',
id: key,
value: progress,
max: max,
},
});
})
.ifForm('number', function(progress) {
chain._struct.push({
type: 'progress',
item: {
type: 'progressBar',
id: 'anonBar',
value: progress,
},
});
})
.ifFormElse(function(form) {
throw new Error('Unsupported call type for async-chainable-progress/progress: ' + form);
});
return this;
};
// }}}
// .spinner() {{{
this.spinner = function() {
var chain = this;
argy(arguments)
.ifForm('', function() {
chain._struct.push({
type: 'progress',
item: {
id: 'anonSpinner',
type: 'spinner',
},
});
})
.ifForm('string', function(key) {
chain._struct.push({
type: 'progress',
item: {
id: key,
type: 'spinner',
},
});
})
.ifForm('null string', function(junk, text) {
chain._struct.push({
type: 'progress',
item: {
type: 'spinner',
id: 'anonSpinner',
text: text,
},
});
})
.ifForm('string string', function(key, text) {
chain._struct.push({
type: 'progress',
item: {
type: 'spinner',
id: key,
text: text,
},
});
})
.ifFormElse(function(form) {
throw new Error('Unsupported call type for async-chainable-progress/spinner: ' + form);
});
return this;
};
// }}}
// .tick() {{{
this.tick = argy('string [string]', function(text, status) {
this._struct.push({type: 'progressTick', text: text, status: status || 'ok'});
this._progressRender();
return this;
});
this._plugins['progressTick'] = function(params) {
console.log(this._progressDefaults.tick[params.status], params.text);
this._progressRender();
this._execute();
};
// }}}
// .progressDefaults() {{{
this._progressDefaults = {
buffer: {
x: 0,
y: 0,
width: 'console',
height: 'console',
},
progress: {
width: 50,
value: 0,
max: 100,
text: null,
},
spinner: {
text: 'Working...',
spinners: process.platform == 'win32' ? ['|','/','-','\\'] : ['◜','◠','◝','◞','◡','◟'],
},
tick: {
ok: '\u001b[32m' + '✓' + '\u001b[0m',
fail: '\u001b[31m' + '✖' + '\u001b[0m',
pending: '\u001b[36m' + '-' + '\u001b[0m',
},
};
this._plugins['progressDefaults'] = function(params) {
this._progressDefaults = params.payload;
this._execute(); // Move onto next chain item
};
this.progressDefaults = function() {
var chain = this;
argy(arguments)
.ifForm('', function() {})
.ifForm('object', function(settings) {
chain._struct.push({
type: 'progressDefaults',
payload: arguments[0],
});
})
.ifFormElse(function(form) {
throw new Error('Unsupported call type for async-chainable-progress/progressDefaults: ' + form);
});
return this;
};
// }}}
// .progressComplete() {{{
this.progressComplete = function() {
var chain = this;
argy(arguments)
.ifForm('', function() {
chain._struct.push({type: 'progressComplete'});
})
.ifForm('string', function(key) {
chain._struct.push({type: 'progressComplete', ids: [key]});
})
.ifForm('array', function(keys) {
chain._struct.push({type: 'progressComplete', ids: keys});
})
.ifFormElse(function(form) {
throw new Error('Unsupported call type for async-chainable-progress/progressComplete: ' + form);
});
return this;
};
this._plugins['progressComplete'] = function(params) {
var removeIds = params.ids || false;
for (var id in this._progressObjects) {
if (!removeIds || !(removeIds.indexOf(id) > -1)) {
if (this._progressObjects[id].rendered++ > 0) terminal.up(1).eraseLineAfter(); // Remove from terminal also
delete this._progressObjects[id];
}
}
this._progressRender();
this._execute();
};
// }}}
// .setProgress() {{{
this.setProgress = function() {
var chain = this;
var progressObj;
argy(arguments)
.ifForm('', function() {
if (! (progressObj = _.find(chain._progressObjects, {id: 'anonBar'})) ) return;
progressObj.value = 0;
})
.ifForm('number', function(progress) {
if (! (progressObj = _.find(chain._progressObjects, {id: 'anonBar'})) ) return;
progressObj.value = progress;
})
.ifForm('number number', function(progress, max) {
if (! (progressObj = _.find(chain._progressObjects, {id: 'anonBar'})) ) return;
progressObj.value = progress;
progressObj.max = max;
})
.ifFormElse(function(form) {
throw new Error('Unsupported call type for async-chainable-progress/setProgress: ' + form);
});
this._progressRender();
return this;
};
// }}}
// Make shortcut for setProgress / progressComplete inside context object
this._context.setProgress = this.setProgress.bind(this);
};