Source: tools/Format.js

const { inspect} = require('util');



/**
 * Formats any value to a useful string that reflects the given value's contents.
 * 
 * Formats like this if val is:
 * - undefined: '<undefined>'
 * - null: '<null>'
 * - typeof string: val (identity)
 * - val.toString() if val's prototype !== null || val's prototype.ctor !== Object
 * - inspect(val), otherwise
 * 
 * @author Sebastian Hönel <development@hoenel.net>
 * @param {any} val
 * @returns {String}
 */
const formatValue = val => {
	if (val === void 0) {
		return '<undefined>';
	} else if (val === null) {
		return '<null>';
	}

	if (typeof val === 'string') {
		return val;
	} else if (Array.isArray(val)) {
		return `[ ${val.map(v => formatValue(v)).join(', ') } ]`;
	} else {
		// We do not want to call toString() on bare Objects where prototype === null
		// or where the Prototype is Object because it will result in [object Object].
		try {
			const proto = Object.getPrototypeOf(val);

			const skipToString = proto === null ||
				('constructor' in proto && proto.constructor === Object);

			if (!skipToString) {
				return val.toString();
			}
		} catch (e) { } // Object.getPrototypeOf threw
	}

	return inspect(val);
};



/**
 * Formats an Error like this if err is (all returned values are prefixed by 'Error: '):
 * - not an Error: return 'Error: formatValue(err)'
 * - an Error: [err.constructor.name] optionally followed by ': ' if the error has a
 *	 message or a stack; If it has a stack, the stack's print is prefixed by 'Stack: '
 * 
 * @author Sebastian Hönel <development@hoenel.net>
 * @param {Error|any} err An instance of Error or any other thrown value.
 * @returns {String}
 */
const formatError = err => {
	if (err instanceof Error) {
		const msg = formatValue(err.message)
		, stack = formatValue(err.stack);

		return `${err.constructor.name}: ${msg}${stack.length > 0 ? ' Stack: ' : ''}${stack}`;
	}

	return `Error: ${formatValue(err)}`;
};


module.exports = Object.freeze({
	formatValue,
	formatError
});