compile.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /**
  2. * Module dependencies
  3. */
  4. var _ = require('@sailshq/lodash');
  5. var dehydrate = require('./dehydrate');
  6. /**
  7. * Given a value, return a human-readable string representing the **value itself**.
  8. * This string is equivalent to a JavaScript code snippet which would accurately represent
  9. * the value in code.
  10. *
  11. * This is a lot like `util.inspect(val, false, null)`, but it also has special
  12. * handling for Errors, Dates, RegExps, and Functions (using `dehydrate()` with
  13. * `allowNull` enabled.) The biggest difference is that everything you get from
  14. * `rttc.compile()` is ready for use as values in `*`, `{}`, or `[]` type machines,
  15. * Treeline, Angular's rendering engine, and JavaScript code in general (i.e. if you
  16. * were to append it on the right-hand side of `var x = `, or if you ran `eval()` on it)
  17. *
  18. * Note that undefined values in arrays and undefined values of keys in dictionaries
  19. * will be stripped out, and circular references will be handled as they are in
  20. * `util.inspect(val, false, null)`
  21. *
  22. * Useful for:
  23. * + generating code samples
  24. * + in particular for bootstrapping data on server-rendered views for access by client-side JavaScript
  25. * + error messages,
  26. * + debugging
  27. * + user interfaces
  28. *
  29. * ~~ Notable differences from `util.inspect()` ~~
  30. * =================================================
  31. *
  32. * | actual | util.inspect() | rttc.compile() |
  33. * | ----------------------- | ----------------------------------------- | -------------------------------------|
  34. * | a function | `[Function: foo]` | `function foo (){}` |
  35. * | a Date | `Tue May 26 2015 20:05:37 GMT-0500 (CDT)` | `'2015-05-27T01:06:37.072Z'` |
  36. * | a RegExp | `/foo/gi` | `'/foo/gi/'` |
  37. * | an Error | `[Error]` | `'Error\n at repl:1:24\n...'` |
  38. * | a deeply nested thing | `{ a: { b: { c: [Object] } } }` | `{ a: { b: { c: { d: {} } } } }` |
  39. * | a circular thing | `{ y: { z: [Circular] } }` | `{ y: { z: '[Circular ~]' } }` |
  40. * | undefined | `undefined` | `null` |
  41. * | Infinity | `Infinity` | `0` |
  42. * | -Infinity | `-Infinity` | `0` |
  43. * | NaN | `NaN` | `0` |
  44. * | Readable (Node stream) | `{ _readableState: { highWaterMar..}}` | `null` |
  45. * | Buffer (Node bytestring)| `<Buffer 61 62 63>` | `null` |
  46. *
  47. *
  48. * ----------------------------------------------------------------------------------------
  49. *
  50. * @param {===} val
  51. * @return {String}
  52. */
  53. module.exports = function compile(val){
  54. return customInspect(dehydrate(val, true, true));
  55. };
  56. //////////////////////////////////////////////////////////////////////////////
  57. // From https://github.com/defunctzombie/node-util/blob/master/util.js#L211 //
  58. // ------------------------------------------------------------------------ //
  59. //////////////////////////////////////////////////////////////////////////////
  60. function customInspect(val) {
  61. // Set up ctx
  62. var ctx = {};
  63. // Default for stylize
  64. ctx.stylize = ctx.stylize || function (val){ return val; };
  65. // Initialize empty 'seen' array
  66. ctx.seen = [];
  67. return formatValue(ctx, val, 100);
  68. }
  69. function formatValue(ctx, value, recurseTimes) {
  70. // Provide a hook for user-specified inspect functions.
  71. // Check that value is an object with an inspect function on it
  72. if (ctx.customInspect &&
  73. value &&
  74. _.isFunction(value.inspect) &&
  75. // Also filter out any prototype objects using the circular check.
  76. !(value.constructor && value.constructor.prototype === value)) {
  77. var ret = value.inspect(recurseTimes, ctx);
  78. if (!_.isString(ret)) {
  79. ret = formatValue(ctx, ret, recurseTimes);
  80. }
  81. return ret;
  82. }
  83. // Primitive types cannot have properties
  84. var primitive = formatPrimitive(ctx, value);
  85. if (primitive) {
  86. return primitive;
  87. }
  88. // Look up the keys of the object.
  89. var keys = Object.keys(value);
  90. var visibleKeys = arrayToHash(keys);
  91. if (ctx.showHidden) {
  92. keys = Object.getOwnPropertyNames(value);
  93. }
  94. // IE doesn't make error fields non-enumerable
  95. // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
  96. if (_.isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
  97. return formatError(value);
  98. }
  99. // Some type of object without properties can be shortcutted.
  100. if (keys.length === 0) {
  101. if (_.isFunction(value)) {
  102. // The classic util.inspect() impl:
  103. // var name = value.name ? ': ' + value.name : '';
  104. // return ctx.stylize('[Function' + name + ']', 'special');
  105. //
  106. // Our impl:
  107. return value.toString();
  108. }
  109. if (_.isRegExp(value)) {
  110. return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
  111. }
  112. if (_.isDate(value)) {
  113. return ctx.stylize(Date.prototype.toString.call(value), 'date');
  114. }
  115. if (_.isError(value)) {
  116. return formatError(value);
  117. }
  118. }
  119. var base = '', array = false, braces = ['{', '}'];
  120. // Make Array say that they are Array
  121. if (_.isArray(value)) {
  122. array = true;
  123. braces = ['[', ']'];
  124. }
  125. // Make functions say that they are functions
  126. if (_.isFunction(value)) {
  127. base = value.toString();
  128. // The classic util.inspect() impl:
  129. // var n = value.name ? ': ' + value.name : '';
  130. // base = ' [Function' + n + ']';
  131. }
  132. // Make RegExps say that they are RegExps
  133. if (_.isRegExp(value)) {
  134. base = ' ' + RegExp.prototype.toString.call(value);
  135. }
  136. // Make dates with properties first say the date
  137. if (_.isDate(value)) {
  138. base = ' ' + Date.prototype.toUTCString.call(value);
  139. }
  140. // Make error with message first say the error
  141. if (_.isError(value)) {
  142. base = ' ' + formatError(value);
  143. }
  144. if (keys.length === 0 && (!array || value.length === 0)) {
  145. return braces[0] + base + braces[1];
  146. }
  147. if (recurseTimes < 0) {
  148. if (_.isRegExp(value)) {
  149. return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
  150. } else {
  151. return ctx.stylize('[Object]', 'special');
  152. }
  153. }
  154. ctx.seen.push(value);
  155. var output;
  156. if (array) {
  157. output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
  158. } else {
  159. output = keys.map(function(key) {
  160. return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
  161. });
  162. }
  163. ctx.seen.pop();
  164. return reduceToSingleString(output, base, braces);
  165. }
  166. function formatPrimitive(ctx, value) {
  167. if (_.isUndefined(value)) {
  168. return ctx.stylize('undefined', 'undefined');
  169. }
  170. if (_.isString(value)) {
  171. var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
  172. .replace(/'/g, "\\'")
  173. .replace(/\\"/g, '"') + '\'';
  174. return ctx.stylize(simple, 'string');
  175. }
  176. if (_.isNumber(value))
  177. return ctx.stylize('' + value, 'number');
  178. if (_.isBoolean(value))
  179. return ctx.stylize('' + value, 'boolean');
  180. // For some reason typeof null is "object", so special case here.
  181. if (_.isNull(value)) {
  182. return ctx.stylize('null', 'null');
  183. }
  184. }
  185. function formatError(value) {
  186. return '[' + Error.prototype.toString.call(value) + ']';
  187. }
  188. function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
  189. var output = [];
  190. for (var i = 0, l = value.length; i < l; ++i) {
  191. if (hasOwnProperty(value, String(i))) {
  192. output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
  193. String(i), true));
  194. } else {
  195. output.push('');
  196. }
  197. }
  198. keys.forEach(function(key) {
  199. if (!key.match(/^\d+$/)) {
  200. output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
  201. key, true));
  202. }
  203. });
  204. return output;
  205. }
  206. function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
  207. var name, str, desc;
  208. desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
  209. if (desc.get) {
  210. if (desc.set) {
  211. str = ctx.stylize('[Getter/Setter]', 'special');
  212. } else {
  213. str = ctx.stylize('[Getter]', 'special');
  214. }
  215. } else {
  216. if (desc.set) {
  217. str = ctx.stylize('[Setter]', 'special');
  218. }
  219. }
  220. if (!hasOwnProperty(visibleKeys, key)) {
  221. name = '[' + key + ']';
  222. }
  223. if (!str) {
  224. if (ctx.seen.indexOf(desc.value) < 0) {
  225. if (_.isNull(recurseTimes)) {
  226. str = formatValue(ctx, desc.value, null);
  227. } else {
  228. str = formatValue(ctx, desc.value, recurseTimes - 1);
  229. }
  230. if (str.indexOf('\n') > -1) {
  231. if (array) {
  232. str = str.split('\n').map(function(line) {
  233. return ' ' + line;
  234. }).join('\n').substr(2);
  235. } else {
  236. str = '\n' + str.split('\n').map(function(line) {
  237. return ' ' + line;
  238. }).join('\n');
  239. }
  240. }
  241. } else {
  242. str = ctx.stylize('[Circular]', 'special');
  243. }
  244. }
  245. if (_.isUndefined(name)) {
  246. if (array && key.match(/^\d+$/)) {
  247. return str;
  248. }
  249. name = JSON.stringify('' + key);
  250. if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
  251. name = name.substr(1, name.length - 2);
  252. name = ctx.stylize(name, 'name');
  253. } else {
  254. name = name.replace(/'/g, "\\'")
  255. .replace(/\\"/g, '"')
  256. .replace(/(^"|"$)/g, "'");
  257. name = ctx.stylize(name, 'string');
  258. }
  259. }
  260. return name + ': ' + str;
  261. }
  262. function reduceToSingleString(output, base, braces) {
  263. var numLinesEst = 0;
  264. var length = output.reduce(function(prev, cur) {
  265. numLinesEst++;
  266. if (cur.indexOf('\n') >= 0) numLinesEst++;
  267. return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
  268. }, 0);
  269. if (length > 60) {
  270. return braces[0] +
  271. (base === '' ? '' : base + '\n ') +
  272. ' ' +
  273. output.join(',\n ') +
  274. ' ' +
  275. braces[1];
  276. }
  277. return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
  278. }
  279. function arrayToHash(array) {
  280. var hash = {};
  281. array.forEach(function(val, idx) {
  282. hash[val] = true;
  283. });
  284. return hash;
  285. }
  286. function hasOwnProperty(obj, prop) {
  287. return Object.prototype.hasOwnProperty.call(obj, prop);
  288. }