extsprintf.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /*
  2. * extsprintf.js: extended POSIX-style sprintf
  3. */
  4. var mod_assert = require('assert');
  5. var mod_util = require('util');
  6. /*
  7. * Public interface
  8. */
  9. exports.sprintf = jsSprintf;
  10. /*
  11. * Stripped down version of s[n]printf(3c). We make a best effort to throw an
  12. * exception when given a format string we don't understand, rather than
  13. * ignoring it, so that we won't break existing programs if/when we go implement
  14. * the rest of this.
  15. *
  16. * This implementation currently supports specifying
  17. * - field alignment ('-' flag),
  18. * - zero-pad ('0' flag)
  19. * - always show numeric sign ('+' flag),
  20. * - field width
  21. * - conversions for strings, decimal integers, and floats (numbers).
  22. * - argument size specifiers. These are all accepted but ignored, since
  23. * Javascript has no notion of the physical size of an argument.
  24. *
  25. * Everything else is currently unsupported, most notably precision, unsigned
  26. * numbers, non-decimal numbers, and characters.
  27. */
  28. function jsSprintf(fmt)
  29. {
  30. var regex = [
  31. '([^%]*)', /* normal text */
  32. '%', /* start of format */
  33. '([\'\\-+ #0]*?)', /* flags (optional) */
  34. '([1-9]\\d*)?', /* width (optional) */
  35. '(\\.([1-9]\\d*))?', /* precision (optional) */
  36. '[lhjztL]*?', /* length mods (ignored) */
  37. '([diouxXfFeEgGaAcCsSp%jr])' /* conversion */
  38. ].join('');
  39. var re = new RegExp(regex);
  40. var args = Array.prototype.slice.call(arguments, 1);
  41. var flags, width, precision, conversion;
  42. var left, pad, sign, arg, match;
  43. var ret = '';
  44. var argn = 1;
  45. mod_assert.equal('string', typeof (fmt));
  46. while ((match = re.exec(fmt)) !== null) {
  47. ret += match[1];
  48. fmt = fmt.substring(match[0].length);
  49. flags = match[2] || '';
  50. width = match[3] || 0;
  51. precision = match[4] || '';
  52. conversion = match[6];
  53. left = false;
  54. sign = false;
  55. pad = ' ';
  56. if (conversion == '%') {
  57. ret += '%';
  58. continue;
  59. }
  60. if (args.length === 0)
  61. throw (new Error('too few args to sprintf'));
  62. arg = args.shift();
  63. argn++;
  64. if (flags.match(/[\' #]/))
  65. throw (new Error(
  66. 'unsupported flags: ' + flags));
  67. if (precision.length > 0)
  68. throw (new Error(
  69. 'non-zero precision not supported'));
  70. if (flags.match(/-/))
  71. left = true;
  72. if (flags.match(/0/))
  73. pad = '0';
  74. if (flags.match(/\+/))
  75. sign = true;
  76. switch (conversion) {
  77. case 's':
  78. if (arg === undefined || arg === null)
  79. throw (new Error('argument ' + argn +
  80. ': attempted to print undefined or null ' +
  81. 'as a string'));
  82. ret += doPad(pad, width, left, arg.toString());
  83. break;
  84. case 'd':
  85. arg = Math.floor(arg);
  86. /*jsl:fallthru*/
  87. case 'f':
  88. sign = sign && arg > 0 ? '+' : '';
  89. ret += sign + doPad(pad, width, left,
  90. arg.toString());
  91. break;
  92. case 'j': /* non-standard */
  93. if (width === 0)
  94. width = 10;
  95. ret += mod_util.inspect(arg, false, width);
  96. break;
  97. case 'r': /* non-standard */
  98. ret += dumpException(arg);
  99. break;
  100. default:
  101. throw (new Error('unsupported conversion: ' +
  102. conversion));
  103. }
  104. }
  105. ret += fmt;
  106. return (ret);
  107. }
  108. function doPad(chr, width, left, str)
  109. {
  110. var ret = str;
  111. while (ret.length < width) {
  112. if (left)
  113. ret += chr;
  114. else
  115. ret = chr + ret;
  116. }
  117. return (ret);
  118. }
  119. /*
  120. * This function dumps long stack traces for exceptions having a cause() method.
  121. * See node-verror for an example.
  122. */
  123. function dumpException(ex)
  124. {
  125. var ret;
  126. if (!(ex instanceof Error))
  127. throw (new Error(jsSprintf('invalid type for %%r: %j', ex)));
  128. /* Note that V8 prepends "ex.stack" with ex.toString(). */
  129. ret = 'EXCEPTION: ' + ex.constructor.name + ': ' + ex.stack;
  130. if (ex.cause && typeof (ex.cause) === 'function') {
  131. var cex = ex.cause();
  132. if (cex) {
  133. ret += '\nCaused by: ' + dumpException(cex);
  134. }
  135. }
  136. return (ret);
  137. }