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