UNPKG

6.78 kBJavaScriptView Raw
1'use strict';
2
3const indent = require('./indent');
4
5const getMaxIndexLength = (input) => {
6 let maxWidth = 0;
7
8 Object.keys(input).forEach((key) => {
9 // Skip undefined values.
10 if (input[key] === undefined)
11 return;
12 maxWidth = Math.max(maxWidth, key.length);
13 });
14
15 return maxWidth;
16};
17
18// Helper function to detect if an object can be directly serializable
19const isSerializable = (input, onlyPrimitives, options) => {
20 if (
21 typeof input === 'boolean'
22 || typeof input === 'number'
23 || typeof input === 'function'
24 || input === null
25 || input instanceof Date
26 )
27 return true;
28
29 if (typeof input === 'string' && input.indexOf('\n') === -1)
30 return true;
31
32
33 if (options.inlineArrays && !onlyPrimitives)
34 if (Array.isArray(input) && isSerializable(input[0], true, options)) {
35 return true;
36 }
37
38
39 return false;
40};
41
42const addColorToData = (input, {
43 colors, stringColor, numberColor,
44}) => {
45 if (typeof input === 'string')
46 // Print strings in regular terminal color
47 return stringColor ? colors[stringColor](input) : input;
48
49
50 const sInput = `${input}`;
51
52 if (input === true)
53 return colors.green(sInput);
54
55 if (input === false)
56 return colors.red(sInput);
57
58 if (input === null)
59 return colors.grey(sInput);
60
61 if (typeof input === 'number')
62 return colors[numberColor](sInput);
63
64 if (typeof input === 'function')
65 return 'function() {}';
66
67
68 if (Array.isArray(input))
69 return input.join(', ');
70
71
72 return sInput;
73};
74
75const indentLines = (string, spaces) => {
76 let lines = string.split('\n');
77 lines = lines.map(line => indent(spaces) + line);
78 return lines.join('\n');
79};
80
81const renderToArray = (data, options, indentation) => {
82 if (isSerializable(data, false, options))
83 return [indent(indentation) + addColorToData(data, options)];
84
85
86 // Unserializable string means it's multiline
87 if (typeof data === 'string')
88 return [
89 `${indent(indentation)}"""`,
90 indentLines(data, indentation + options.defaultIndentation),
91 `${indent(indentation)}"""`,
92 ];
93
94
95 if (Array.isArray(data)) {
96 // If the array is empty, render the `emptyArrayMsg`
97 if (data.length === 0)
98 return [indent(indentation) + options.emptyArrayMsg];
99
100
101 const outputArray = [];
102
103 data.forEach((element) => {
104 // Prepend the dash at the beginning of each array's element line
105 let line = '- ';
106 line = options.colors[options.dashColor](line);
107
108 line = indent(indentation) + line;
109
110 // If the element of the array is a string, bool, number, or null
111 // render it in the same line
112 if (isSerializable(element, false, options)) {
113 line += renderToArray(element, options, 0)[0];
114 outputArray.push(line);
115
116 // If the element is an array or object, render it in next line
117 } else {
118 outputArray.push(line);
119 outputArray.push(...renderToArray(element, options, indentation + options.defaultIndentation));
120 }
121 });
122
123 return outputArray;
124 }
125
126 if (data instanceof Error)
127 return renderToArray(
128 {
129 message: data.message,
130 stack: data.stack.split('\n'),
131 },
132 options,
133 indentation,
134 );
135
136
137 // If values alignment is enabled, get the size of the longest index
138 // to align all the values
139 const maxIndexLength = options.noAlign ? 0 : getMaxIndexLength(data);
140 let key;
141 const output = [];
142
143 Object.keys(data).forEach((i) => {
144 // Prepend the index at the beginning of the line
145 key = (`${i}: `);
146 key = options.colors[options.keysColor](key);
147
148 key = indent(indentation, key);
149
150 // Skip `undefined`, it's not a valid JSON value.
151 if (data[i] === undefined)
152 return;
153
154
155 // If the value is serializable, render it in the same line
156 if (isSerializable(data[i], false, options)) {
157 const nextIndentation = options.noAlign ? 0 : maxIndexLength - i.length;
158 key += renderToArray(data[i], options, nextIndentation)[0];
159 output.push(key);
160
161 // If the index is an array or object, render it in next line
162 } else {
163 output.push(key);
164 output.push(...renderToArray(
165 data[i],
166 options,
167 indentation + options.defaultIndentation,
168 ));
169 }
170 });
171 return output;
172};
173
174// ### Render function
175// *Parameters:*
176//
177// * **`data`**: Data to render
178// * **`options`**: Hash with different options to configure the parser
179// * **`indentation`**: Base indentation of the parsed output
180//
181// *Example of options hash:*
182//
183// {
184// emptyArrayMsg: '(empty)', // Rendered message on empty strings
185// keysColor: 'blue', // Color for keys in hashes
186// dashColor: 'red', // Color for the dashes in arrays
187// stringColor: 'grey', // Color for strings
188// defaultIndentation: 2 // Indentation on nested objects
189// }
190const render = function render(data, indentation, {
191 emptyArrayMsg = '(empty array)',
192 keysColor = 'green',
193 dashColor = 'green',
194 numberColor = 'blue',
195 defaultIndentation = 2,
196 noAlign = false,
197 stringColor = null,
198 colors,
199}) {
200 if (!colors)
201 throw new Error('Colors library required');
202
203 // Default values
204 indentation = indentation || 0;
205
206 return renderToArray(data, {
207 emptyArrayMsg,
208 keysColor,
209 dashColor,
210 numberColor,
211 defaultIndentation,
212 noAlign,
213 stringColor,
214 colors,
215 }, indentation).join('\n');
216};
217
218// ### Render from string function
219// *Parameters:*
220//
221// * **`data`**: Data to render as a string
222// * **`options`**: Hash with different options to configure the parser
223// * **`indentation`**: Base indentation of the parsed output
224//
225// *Example of options hash:*
226//
227// {
228// emptyArrayMsg: '(empty)', // Rendered message on empty strings
229// keysColor: 'blue', // Color for keys in hashes
230// dashColor: 'red', // Color for the dashes in arrays
231// defaultIndentation: 2 // Indentation on nested objects
232// }
233const renderString = function renderString(data, options, indentation) {
234 let output = '';
235 let parsedData;
236 // If the input is not a string or if it's empty, just return an empty string
237 if (typeof data !== 'string' || data === '')
238 return '';
239
240
241 // Remove non-JSON characters from the beginning string
242 if (data[0] !== '{' && data[0] !== '[') {
243 let beginningOfJson;
244 if (data.indexOf('{') === -1)
245 beginningOfJson = data.indexOf('[');
246 else if (data.indexOf('[') === -1)
247 beginningOfJson = data.indexOf('{');
248 else if (data.indexOf('{') < data.indexOf('['))
249 beginningOfJson = data.indexOf('{');
250 else
251 beginningOfJson = data.indexOf('[');
252
253 output += `${data.substr(0, beginningOfJson)}\n`;
254 data = data.substr(beginningOfJson);
255 }
256
257 try {
258 parsedData = JSON.parse(data);
259 } catch (e) {
260 // Return an error in case of an invalid JSON
261 return `${options.colors.red('Error:')} Not valid JSON!`;
262 }
263
264 // Call the real render() method
265 output += exports.render(parsedData, options, indentation);
266 return output;
267};
268
269module.exports = {
270 render,
271 renderString,
272};