@absolunet/ioc2.1.0

View on GitHub

console/services/Interceptor.js

//--------------------------------------------------------
//-- Node IoC - Console - Services - Interceptor
//--------------------------------------------------------

import __ from '@absolunet/private-registry';


/**
 * The stdout interceptor.
 *
 * @memberof console.services
 * @hideconstructor
 */
class Interceptor {

	/**
	 * Class dependencies: <code>['terminal.interceptor.capture']</code>.
	 *
	 * @type {Array<string>}
	 */
	static get dependencies() {
		return ['terminal.interceptor.capture'];
	}

	/**
	 * @inheritdoc
	 * @private
	 */
	init() {
		const interceptors = [];
		__(this).set('interceptors', interceptors);
		__(this).set('interceptor', (content) => {
			const editable      = __(this).get('editable');
			const editedContent = interceptors.reduce((value, closure) => {
				const transformedValue = closure(this.styleCleaner(value));

				return editable && typeof transformedValue !== 'undefined' ? transformedValue : value;
			}, content);

			return __(this).get('mute') ? '' : editedContent;
		});

		this
			.enable()
			.unmute()
			.removeStyle()
			.uneditable();
	}

	/**
	 * Add an stdout interceptor.
	 *
	 * @param {Function} closure - The interceptor closure.
	 * @param {number|null} [index] - The index where the interceptor should be inserted.
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	add(closure, index = null) {
		const interceptors = __(this).get('interceptors');
		interceptors.splice(index === null ? interceptors.length : index, 0, closure);

		return this;
	}

	/**
	 * Remove an stdout interceptor.
	 *
	 * @param {Function} closure - The interceptor closure.
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	remove(closure) {
		const interceptors = __(this).get('interceptors');

		if (interceptors.includes(closure)) {
			interceptors.splice(interceptors.indexOf(closure), 1);
		}

		return this;
	}

	/**
	 * Remove all existing interceptors.
	 *
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	removeAll() {
		__(this).get('interceptors').splice(0);

		return this;
	}

	/**
	 * Start capture.
	 *
	 * @param {boolean} [mute=true] - Indicates if the console should be muted.
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	startCapture(mute = true) {
		const { captureInterceptor } = this;

		captureInterceptor.flush();

		this.add(captureInterceptor.handler);

		if (mute) {
			this.mute();
		}

		return this;
	}

	/**
	 * Stop capture and get the output.
	 *
	 * @param {boolean} [unmute=true] - Indicates if the console should be unmuted.
	 * @returns {Array<string>} The captured data.
	 */
	stopCapture(unmute = true) {
		this.remove(this.captureInterceptor.handler);

		if (unmute) {
			this.unmute();
		}

		return this.output;
	}

	/**
	 * Enable interceptors.
	 *
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	enable() {
		if (!__(this).get('disable')) {
			__(this).set('disable', this.interceptStdout((content) => {
				return __(this).get('interceptor')(content);
			}));
		}

		return this;
	}

	/**
	 * Disable interceptors.
	 *
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	disable() {
		const disable = __(this).get('disable');

		if (disable) {
			disable();
			__(this).set('disable', undefined);
		}

		return this;
	}

	/**
	 * Mute stdout in process but keep interceptors enabled.
	 *
	 * @param {boolean} [state=true] - Indicates the mute state.
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	mute(state = true) {
		__(this).set('mute', state);

		return this;
	}

	/**
	 * Unmute stdout in process.
	 *
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	unmute() {
		return this.mute(false);
	}

	/**
	 * Allow interceptors to edit output content.
	 *
	 * @param {boolean} [state] - Indicates the editable state.
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	editable(state = true) {
		__(this).set('editable', state);

		return this;
	}

	/**
	 * Deny interceptors to edit output content.
	 *
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	uneditable() {
		this.editable(false);

		return this;
	}

	/**
	 * Keep styles in content when passing it to interceptors.
	 *
	 * @param {boolean} [state] - Indicates if the style should be kept.
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	keepStyle(state = true) {
		__(this).set('style', state);

		return this;
	}

	/**
	 * Remove styles in content when passing it to interceptor.
	 *
	 * @returns {console.services.Interceptor} The Interceptor service instance.
	 */
	removeStyle() {
		this.keepStyle(false);

		return this;
	}

	/**
	 * Style cleaner.
	 *
	 * @see keepStyle
	 * @see removeStyle
	 * @type {Function}
	 */
	get styleCleaner() {
		return (content) => {
			if (__(this).get('style')) {
				return content;
			}

			return content.replace(this.ansiRegexFactory(), '');
		};
	}

	/**
	 * Capture interceptor instance.
	 *
	 * @type {console.interceptors.CaptureInterceptor}
	 */
	get captureInterceptor() {
		return __(this).get('terminal.interceptor.capture');
	}

	/**
	 * Captured output.
	 *
	 * @type {Array<string>}
	 */
	get output() {
		return this.captureInterceptor.capture;
	}

	/**
	 * The intercept-stdout module.
	 *
	 * @type {Function}
	 */
	get interceptStdout() {
		return require('intercept-stdout'); // eslint-disable-line global-require
	}

	/**
	 * The ansi-regex module.
	 *
	 * @type {Function}
	 */
	get ansiRegexFactory() {
		return require('ansi-regex'); // eslint-disable-line global-require
	}

}


export default Interceptor;