sprintf.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /**
  2. * Copyright (c) 2007-2024 Kevin van Zonneveld (https://kvz.io)
  3. * and Contributors (https://locutus.io/authors)
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy of
  6. * this software and associated documentation files (the "Software"), to deal in
  7. * the Software without restriction, including without limitation the rights to
  8. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  9. * of the Software, and to permit persons to whom the Software is furnished to do
  10. * so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in all
  13. * copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. * SOFTWARE.
  22. */
  23. function sprintf () {
  24. // discuss at: https://locutus.io/php/sprintf/
  25. // original by: Ash Searle (https://hexmen.com/blog/)
  26. // improved by: Michael White (https://getsprink.com)
  27. // improved by: Jack
  28. // improved by: Kevin van Zonneveld (https://kvz.io)
  29. // improved by: Kevin van Zonneveld (https://kvz.io)
  30. // improved by: Kevin van Zonneveld (https://kvz.io)
  31. // improved by: Dj
  32. // improved by: Allidylls
  33. // input by: Paulo Freitas
  34. // input by: Brett Zamir (https://brett-zamir.me)
  35. // improved by: Rafał Kukawski (https://kukawski.pl)
  36. // example 1: sprintf("%01.2f", 123.1)
  37. // returns 1: '123.10'
  38. // example 2: sprintf("[%10s]", 'monkey')
  39. // returns 2: '[ monkey]'
  40. // example 3: sprintf("[%'#10s]", 'monkey')
  41. // returns 3: '[####monkey]'
  42. // example 4: sprintf("%d", 123456789012345)
  43. // returns 4: '123456789012345'
  44. // example 5: sprintf('%-03s', 'E')
  45. // returns 5: 'E00'
  46. // example 6: sprintf('%+010d', 9)
  47. // returns 6: '+000000009'
  48. // example 7: sprintf('%+0\'@10d', 9)
  49. // returns 7: '@@@@@@@@+9'
  50. // example 8: sprintf('%.f', 3.14)
  51. // returns 8: '3.140000'
  52. // example 9: sprintf('%% %2$d', 1, 2)
  53. // returns 9: '% 2'
  54. var regex = /%%|%(?:(\d+)\$)?((?:[-+#0 ]|'[\s\S])*)(\d+)?(?:\.(\d*))?([\s\S])/g
  55. var args = arguments
  56. var i = 0
  57. var format = args[i++]
  58. var _pad = function (str, len, chr, leftJustify) {
  59. if (!chr) {
  60. chr = ' '
  61. }
  62. const padding = str.length >= len ? '' : new Array((1 + len - str.length) >>> 0).join(chr)
  63. return leftJustify ? str + padding : padding + str
  64. }
  65. var justify = function (value, prefix, leftJustify, minWidth, padChar) {
  66. var diff = minWidth - value.length
  67. if (diff > 0) {
  68. // when padding with zeros
  69. // on the left side
  70. // keep sign (+ or -) in front
  71. if (!leftJustify && padChar === '0') {
  72. value = [value.slice(0, prefix.length), _pad('', diff, '0', true), value.slice(prefix.length)].join('')
  73. } else {
  74. value = _pad(value, minWidth, padChar, leftJustify)
  75. }
  76. }
  77. return value
  78. }
  79. var _formatBaseX = function (value, base, leftJustify, minWidth, precision, padChar) {
  80. // Note: casts negative numbers to positive ones
  81. var number = value >>> 0
  82. value = _pad(number.toString(base), precision || 0, '0', false)
  83. return justify(value, '', leftJustify, minWidth, padChar)
  84. }
  85. // _formatString()
  86. var _formatString = function (value, leftJustify, minWidth, precision, customPadChar) {
  87. if (precision !== null && precision !== undefined) {
  88. value = value.slice(0, precision)
  89. }
  90. return justify(value, '', leftJustify, minWidth, customPadChar)
  91. }
  92. // doFormat()
  93. var doFormat = function (substring, argIndex, modifiers, minWidth, precision, specifier) {
  94. var number, prefix, method, textTransform, value
  95. if (substring === '%%') {
  96. return '%'
  97. }
  98. // parse modifiers
  99. var padChar = ' ' // pad with spaces by default
  100. var leftJustify = false
  101. var positiveNumberPrefix = ''
  102. var j, l
  103. for (j = 0, l = modifiers.length; j < l; j++) {
  104. switch (modifiers.charAt(j)) {
  105. case ' ':
  106. case '0':
  107. padChar = modifiers.charAt(j)
  108. break
  109. case '+':
  110. positiveNumberPrefix = '+'
  111. break
  112. case '-':
  113. leftJustify = true
  114. break
  115. case "'":
  116. if (j + 1 < l) {
  117. padChar = modifiers.charAt(j + 1)
  118. j++
  119. }
  120. break
  121. }
  122. }
  123. if (!minWidth) {
  124. minWidth = 0
  125. } else {
  126. minWidth = +minWidth
  127. }
  128. if (!isFinite(minWidth)) {
  129. throw new Error('Width must be finite')
  130. }
  131. if (!precision) {
  132. precision = specifier === 'd' ? 0 : 'fFeE'.indexOf(specifier) > -1 ? 6 : undefined
  133. } else {
  134. precision = +precision
  135. }
  136. if (argIndex && +argIndex === 0) {
  137. throw new Error('Argument number must be greater than zero')
  138. }
  139. if (argIndex && +argIndex >= args.length) {
  140. throw new Error('Too few arguments')
  141. }
  142. value = argIndex ? args[+argIndex] : args[i++]
  143. switch (specifier) {
  144. case '%':
  145. return '%'
  146. case 's':
  147. return _formatString(value + '', leftJustify, minWidth, precision, padChar)
  148. case 'c':
  149. return _formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, padChar)
  150. case 'b':
  151. return _formatBaseX(value, 2, leftJustify, minWidth, precision, padChar)
  152. case 'o':
  153. return _formatBaseX(value, 8, leftJustify, minWidth, precision, padChar)
  154. case 'x':
  155. return _formatBaseX(value, 16, leftJustify, minWidth, precision, padChar)
  156. case 'X':
  157. return _formatBaseX(value, 16, leftJustify, minWidth, precision, padChar).toUpperCase()
  158. case 'u':
  159. return _formatBaseX(value, 10, leftJustify, minWidth, precision, padChar)
  160. case 'i':
  161. case 'd':
  162. number = +value || 0
  163. // Plain Math.round doesn't just truncate
  164. number = Math.round(number - (number % 1))
  165. prefix = number < 0 ? '-' : positiveNumberPrefix
  166. value = prefix + _pad(String(Math.abs(number)), precision, '0', false)
  167. if (leftJustify && padChar === '0') {
  168. // can't right-pad 0s on integers
  169. padChar = ' '
  170. }
  171. return justify(value, prefix, leftJustify, minWidth, padChar)
  172. case 'e':
  173. case 'E':
  174. case 'f': // @todo: Should handle locales (as per setlocale)
  175. case 'F':
  176. case 'g':
  177. case 'G':
  178. number = +value
  179. prefix = number < 0 ? '-' : positiveNumberPrefix
  180. method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(specifier.toLowerCase())]
  181. textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(specifier) % 2]
  182. value = prefix + Math.abs(number)[method](precision)
  183. return justify(value, prefix, leftJustify, minWidth, padChar)[textTransform]()
  184. default:
  185. // unknown specifier, consume that char and return empty
  186. return ''
  187. }
  188. }
  189. try {
  190. return format.replace(regex, doFormat)
  191. } catch (err) {
  192. return false
  193. }
  194. }