source/utils/PrettyFormat.js
import typeDetect from "./typeDetect";
/**
* Pretty format any javascript object.
*
* Handles the following types:
*
* - null
* - undefined
* - Number
* - Boolean
* - String
* - Array
* - Map
* - Set
* - Function
* - Class (detected as a Function, so pretty formatted just like a function)
* - Object
*
* @example <caption>Without indentation</caption>
* new PrettyFormat([1, 2]).toString();
*
* @example <caption>With indentation (indent by 2 spaces)</caption>
* new PrettyFormat([1, 2]).toString(2);
*
* @example <caption>Simple examples</caption>
* new PrettyFormat(true).toString() === 'true';
* new PrettyFormat(null).toString() === 'null';
* new PrettyFormat([1, 2]).toString() === '[1, 2]';
* new PrettyFormat({name: "John", age: 29}).toString() === '{"age": 29, "name": John}';
*
* @example <caption>Complex example</caption>
* let map = new Map();
* map.set('a', [10, 20]);
* map.set('b', [30, 40, 50]);
* function testFunction() {}
* let obj = {
* theMap: map,
* aSet: new Set(['one', 'two']),
* theFunction: testFunction
* };
* const prettyFormatted = new PrettyFormat(obj).toString(2);
*/
export default class PrettyFormat {
constructor(obj) {
this._obj = obj;
}
_indentString(str, indent, indentLevel) {
if(indent === 0) {
return str;
}
return `${' '.repeat(indent * indentLevel)}${str}`;
}
_objectToMap(obj) {
let map = new Map();
let sortedKeys = Array.from(Object.keys(obj));
sortedKeys.sort();
for(let key of sortedKeys) {
map.set(key, obj[key]);
}
return map;
}
_prettyFormatFlatIterable(flatIterable, size, indent, indentLevel, prefix, suffix) {
let output = prefix;
let itemSuffix = ', ';
if(indent) {
output = `${prefix}\n`;
itemSuffix = ',';
}
let index = 1;
for(let item of flatIterable) {
let prettyItem = this._prettyFormat(item, indent, indentLevel + 1);
if(index !== size) {
prettyItem += itemSuffix;
}
output += this._indentString(prettyItem, indent, indentLevel + 1);
if(indent) {
output += '\n';
}
index ++;
}
output += this._indentString(`${suffix}`, indent, indentLevel);
return output;
}
_prettyFormatMap(map, indent, indentLevel, prefix, suffix, keyValueSeparator) {
let output = prefix;
let itemSuffix = ', ';
if(indent) {
output = `${prefix}\n`;
itemSuffix = ',';
}
let index = 1;
for(let [key, value] of map) {
let prettyKey = this._prettyFormat(key, indent, indentLevel + 1);
let prettyValue = this._prettyFormat(value, indent, indentLevel + 1);
let prettyItem = `${prettyKey}${keyValueSeparator}${prettyValue}`;
if(index !== map.size) {
prettyItem += itemSuffix;
}
output += this._indentString(prettyItem, indent, indentLevel + 1);
if(indent) {
output += '\n';
}
index ++;
}
output += this._indentString(`${suffix}`, indent, indentLevel);
return output;
}
_prettyFormatFunction(fn) {
return `[Function: ${fn.name}]`;
}
_prettyFormat(obj, indent, indentLevel) {
const typeString = typeDetect(obj);
let output = '';
if(typeString === 'string') {
output = `"${obj}"`;
} else if(typeString === 'number' || typeString === 'boolean' ||
typeString === 'undefined' || typeString === 'null') {
output = `${obj}`;
} else if(typeString === 'array') {
output = this._prettyFormatFlatIterable(obj, obj.length, indent, indentLevel, '[', ']');
} else if(typeString === 'set') {
output = this._prettyFormatFlatIterable(obj, obj.size, indent, indentLevel, 'Set(', ')');
} else if(typeString === 'map') {
output = this._prettyFormatMap(obj, indent, indentLevel, 'Map(', ')', ' => ');
} else if(typeString === 'function') {
output = this._prettyFormatFunction(obj);
} else {
output = this._prettyFormatMap(this._objectToMap(obj), indent, indentLevel, '{', '}', ': ');
}
return output;
}
/**
* Get the results as a string, optionally indented.
*
* @param {number} indent The number of spaces to indent by. Only
* child objects are indented, and they are indented recursively.
* @returns {string}
*/
toString(indent) {
indent = indent || 0;
return this._prettyFormat(this._obj, indent, 0);
}
}