src/Writers/CliOutput.js
const assert = require('assert');
const stream = require('stream');
const Handler = require('../Handler');
const Writer = require('../Writer');
const MeboErrors = require('../MeboErrors');
// symbols used for private members to avoid any potential clashing
// caused by re-implementations
const _stdout = Symbol('stdout');
const _stderr = Symbol('stderr');
/**
* Command output writer.
*
* This writer is used by the output of the cli handler
* ({@link Cli}).
*
* In case the value is an exception then it's treated as
* {@link CliOutput._errorOutput} otherwise the value is treated as
* {@link CliOutput._successOutput}.
*
* When an action is executed through a handler it can define options for
* readers and writers via {@link Metadata} support. For instance,
* you can use it to provide a custom result for a specific handler:
*
* ```
* class MyAction extends Mebo.Action{
*
* // ...
*
* async _perform(data){
* // ...
* }
*
* async _after(err, value){
* // defining a custom result that only affects the web handler
* // this call could be done inside of the _perform method. However, we
* // are defining it inside of the _after to keep _perform as
* // abstract as possible. Since, _after is always called (even during
* // an error) after the execution of the action, it provides a way to
* // hook and define custom metadata related with the result.
* if (!err){
* // defining a custom output option
* this.setMeta('$cliResult', {
* message: 'My custom cli result!',
* });
* }
* }
*
* // ...
* }
* ```
*
* <h2>Options Summary</h2>
*
* Option Name | Description | Default Value
* --- | --- | :---:
* result | Overrides the value returned by {@link Writer.value} to an arbitrary \
* value (only affects the success output) |
*/
class CliOutput extends Writer{
/**
* Creates the cli output writer
*
* @param {*} value - arbitrary value passed to the writer
* @param {stream} stdout - stream used as stdout
* @param {stream} stderr - stream used as stderr
*/
constructor(value, stdout, stderr){
super(value);
this._setStdout(stdout);
this._setStderr(stderr);
}
/**
* Returns the stream used as stdout
*
* @return {stream}
*/
stdout(){
return this[_stdout];
}
/**
* Returns the stream used as stderr
*
* @return {stream}
*/
stderr(){
return this[_stderr];
}
/**
* Implements the response for an error value.
*
* The error output writes the error message under the stderr.
*
* @protected
*/
_errorOutput(){
process.exitCode = 1;
const message = super._errorOutput();
if (this.value() instanceof MeboErrors.Help){
this.stderr().write(`${message}\n`);
}
else{
this.stderr().write(message);
}
}
/**
* Implements the response for a success value.
*
* Readable streams are piped to {@link Cli.stdout}, otherwise
* the value is serialized using json.
*
* @protected
*/
_successOutput(){
let result = super._successOutput();
/* istanbul ignore next */
if (result === undefined){
return;
}
// readable stream
if (result instanceof stream.Readable){
result.pipe(this.stdout());
return;
}
// json result
result = JSON.stringify(result, null, ' ');
result += '\n';
this.stdout().write(result);
}
/**
* Sets the stdout stream
*
* @param {stream} value - stream used as stdout
* @private
*/
_setStdout(value){
assert(value instanceof stream, 'Invalid stream type');
this[_stdout] = value;
}
/**
* Sets the stderr stream
*
* @param {stream} value - stream used as stderr
* @private
*/
_setStderr(value){
assert(value instanceof stream, 'Invalid stream type');
this[_stderr] = value;
}
}
// registering writer
Handler.registerWriter(CliOutput, 'cli');
module.exports = CliOutput;