sprintf.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /* global window, exports, define */
  2. !function() {
  3. 'use strict'
  4. var re = {
  5. not_string: /[^s]/,
  6. not_bool: /[^t]/,
  7. not_type: /[^T]/,
  8. not_primitive: /[^v]/,
  9. number: /[diefg]/,
  10. numeric_arg: /[bcdiefguxX]/,
  11. json: /[j]/,
  12. not_json: /[^j]/,
  13. text: /^[^\x25]+/,
  14. modulo: /^\x25{2}/,
  15. placeholder: /^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,
  16. key: /^([a-z_][a-z_\d]*)/i,
  17. key_access: /^\.([a-z_][a-z_\d]*)/i,
  18. index_access: /^\[(\d+)\]/,
  19. sign: /^[+-]/
  20. }
  21. function sprintf(key) {
  22. // `arguments` is not an array, but should be fine for this call
  23. return sprintf_format(sprintf_parse(key), arguments)
  24. }
  25. function vsprintf(fmt, argv) {
  26. return sprintf.apply(null, [fmt].concat(argv || []))
  27. }
  28. function sprintf_format(parse_tree, argv) {
  29. var cursor = 1, tree_length = parse_tree.length, arg, output = '', i, k, ph, pad, pad_character, pad_length, is_positive, sign
  30. for (i = 0; i < tree_length; i++) {
  31. if (typeof parse_tree[i] === 'string') {
  32. output += parse_tree[i]
  33. }
  34. else if (typeof parse_tree[i] === 'object') {
  35. ph = parse_tree[i] // convenience purposes only
  36. if (ph.keys) { // keyword argument
  37. arg = argv[cursor]
  38. for (k = 0; k < ph.keys.length; k++) {
  39. if (arg == undefined) {
  40. throw new Error(sprintf('[sprintf] Cannot access property "%s" of undefined value "%s"', ph.keys[k], ph.keys[k-1]))
  41. }
  42. arg = arg[ph.keys[k]]
  43. }
  44. }
  45. else if (ph.param_no) { // positional argument (explicit)
  46. arg = argv[ph.param_no]
  47. }
  48. else { // positional argument (implicit)
  49. arg = argv[cursor++]
  50. }
  51. if (re.not_type.test(ph.type) && re.not_primitive.test(ph.type) && arg instanceof Function) {
  52. arg = arg()
  53. }
  54. if (re.numeric_arg.test(ph.type) && (typeof arg !== 'number' && isNaN(arg))) {
  55. throw new TypeError(sprintf('[sprintf] expecting number but found %T', arg))
  56. }
  57. if (re.number.test(ph.type)) {
  58. is_positive = arg >= 0
  59. }
  60. switch (ph.type) {
  61. case 'b':
  62. arg = parseInt(arg, 10).toString(2)
  63. break
  64. case 'c':
  65. arg = String.fromCharCode(parseInt(arg, 10))
  66. break
  67. case 'd':
  68. case 'i':
  69. arg = parseInt(arg, 10)
  70. break
  71. case 'j':
  72. arg = JSON.stringify(arg, null, ph.width ? parseInt(ph.width) : 0)
  73. break
  74. case 'e':
  75. arg = ph.precision ? parseFloat(arg).toExponential(ph.precision) : parseFloat(arg).toExponential()
  76. break
  77. case 'f':
  78. arg = ph.precision ? parseFloat(arg).toFixed(ph.precision) : parseFloat(arg)
  79. break
  80. case 'g':
  81. arg = ph.precision ? String(Number(arg.toPrecision(ph.precision))) : parseFloat(arg)
  82. break
  83. case 'o':
  84. arg = (parseInt(arg, 10) >>> 0).toString(8)
  85. break
  86. case 's':
  87. arg = String(arg)
  88. arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
  89. break
  90. case 't':
  91. arg = String(!!arg)
  92. arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
  93. break
  94. case 'T':
  95. arg = Object.prototype.toString.call(arg).slice(8, -1).toLowerCase()
  96. arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
  97. break
  98. case 'u':
  99. arg = parseInt(arg, 10) >>> 0
  100. break
  101. case 'v':
  102. arg = arg.valueOf()
  103. arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
  104. break
  105. case 'x':
  106. arg = (parseInt(arg, 10) >>> 0).toString(16)
  107. break
  108. case 'X':
  109. arg = (parseInt(arg, 10) >>> 0).toString(16).toUpperCase()
  110. break
  111. }
  112. if (re.json.test(ph.type)) {
  113. output += arg
  114. }
  115. else {
  116. if (re.number.test(ph.type) && (!is_positive || ph.sign)) {
  117. sign = is_positive ? '+' : '-'
  118. arg = arg.toString().replace(re.sign, '')
  119. }
  120. else {
  121. sign = ''
  122. }
  123. pad_character = ph.pad_char ? ph.pad_char === '0' ? '0' : ph.pad_char.charAt(1) : ' '
  124. pad_length = ph.width - (sign + arg).length
  125. pad = ph.width ? (pad_length > 0 ? pad_character.repeat(pad_length) : '') : ''
  126. output += ph.align ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
  127. }
  128. }
  129. }
  130. return output
  131. }
  132. var sprintf_cache = Object.create(null)
  133. function sprintf_parse(fmt) {
  134. if (sprintf_cache[fmt]) {
  135. return sprintf_cache[fmt]
  136. }
  137. var _fmt = fmt, match, parse_tree = [], arg_names = 0
  138. while (_fmt) {
  139. if ((match = re.text.exec(_fmt)) !== null) {
  140. parse_tree.push(match[0])
  141. }
  142. else if ((match = re.modulo.exec(_fmt)) !== null) {
  143. parse_tree.push('%')
  144. }
  145. else if ((match = re.placeholder.exec(_fmt)) !== null) {
  146. if (match[2]) {
  147. arg_names |= 1
  148. var field_list = [], replacement_field = match[2], field_match = []
  149. if ((field_match = re.key.exec(replacement_field)) !== null) {
  150. field_list.push(field_match[1])
  151. while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
  152. if ((field_match = re.key_access.exec(replacement_field)) !== null) {
  153. field_list.push(field_match[1])
  154. }
  155. else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
  156. field_list.push(field_match[1])
  157. }
  158. else {
  159. throw new SyntaxError('[sprintf] failed to parse named argument key')
  160. }
  161. }
  162. }
  163. else {
  164. throw new SyntaxError('[sprintf] failed to parse named argument key')
  165. }
  166. match[2] = field_list
  167. }
  168. else {
  169. arg_names |= 2
  170. }
  171. if (arg_names === 3) {
  172. throw new Error('[sprintf] mixing positional and named placeholders is not (yet) supported')
  173. }
  174. parse_tree.push(
  175. {
  176. placeholder: match[0],
  177. param_no: match[1],
  178. keys: match[2],
  179. sign: match[3],
  180. pad_char: match[4],
  181. align: match[5],
  182. width: match[6],
  183. precision: match[7],
  184. type: match[8]
  185. }
  186. )
  187. }
  188. else {
  189. throw new SyntaxError('[sprintf] unexpected placeholder')
  190. }
  191. _fmt = _fmt.substring(match[0].length)
  192. }
  193. return sprintf_cache[fmt] = parse_tree
  194. }
  195. /**
  196. * export to either browser or node.js
  197. */
  198. /* eslint-disable quote-props */
  199. if (typeof exports !== 'undefined') {
  200. exports['sprintf'] = sprintf
  201. exports['vsprintf'] = vsprintf
  202. }
  203. if (typeof window !== 'undefined') {
  204. window['sprintf'] = sprintf
  205. window['vsprintf'] = vsprintf
  206. if (typeof define === 'function' && define['amd']) {
  207. define(function() {
  208. return {
  209. 'sprintf': sprintf,
  210. 'vsprintf': vsprintf
  211. }
  212. })
  213. }
  214. }
  215. /* eslint-enable quote-props */
  216. }(); // eslint-disable-line