@@ -10,6 +10,7 @@ module.exports = function sprintf () {
10
10
// improved by: Allidylls
11
11
// input by: Paulo Freitas
12
12
// input by: Brett Zamir (http://brett-zamir.me)
13
+ // improved by: Rafał Kukawski (http://kukawski.pl)
13
14
// example 1: sprintf("%01.2f", 123.1)
14
15
// returns 1: '123.10'
15
16
// example 2: sprintf("[%10s]", 'monkey')
@@ -20,11 +21,19 @@ module.exports = function sprintf () {
20
21
// returns 4: '123456789012345'
21
22
// example 5: sprintf('%-03s', 'E')
22
23
// returns 5: 'E00'
24
+ // example 6: sprintf('%+010d', 9)
25
+ // returns 6: '+000000009'
26
+ // example 7: sprintf('%+0\'@10d', 9)
27
+ // returns 7: '@@@@@@@@+9'
28
+ // example 8: sprintf('%.f', 3.14)
29
+ // returns 8: '3.140000'
30
+ // example 9: sprintf('%% %2$d', 1, 2)
31
+ // returns 9: '% 2'
23
32
24
- var regex = / % % | % ( \d + \$ ) ? ( [ - + ' # 0 ] * ) ( \* \d + \$ | \* | \d + ) ? (?: \. ( \d * ) ) ? ( [ s c b o x X u i d e E f F g G ] ) / g
25
- var a = arguments
33
+ var regex = / % % | % (?: ( \d + ) \$ ) ? ( (?: [ - + # 0 ] | ' [ \s \S ] ) * ) ( \d + ) ? (?: \. ( \d * ) ) ? ( [ \s \S ] ) / g
34
+ var args = arguments
26
35
var i = 0
27
- var format = a [ i ++ ]
36
+ var format = args [ i ++ ]
28
37
29
38
var _pad = function ( str , len , chr , leftJustify ) {
30
39
if ( ! chr ) {
@@ -34,153 +43,153 @@ module.exports = function sprintf () {
34
43
return leftJustify ? str + padding : padding + str
35
44
}
36
45
37
- var justify = function ( value , prefix , leftJustify , minWidth , zeroPad , customPadChar ) {
46
+ var justify = function ( value , prefix , leftJustify , minWidth , padChar ) {
38
47
var diff = minWidth - value . length
39
48
if ( diff > 0 ) {
40
- if ( leftJustify || ! zeroPad ) {
41
- value = _pad ( value , minWidth , customPadChar , leftJustify )
42
- } else {
49
+ // when padding with zeros
50
+ // on the left side
51
+ // keep sign (+ or -) in front
52
+ if ( ! leftJustify && padChar === '0' ) {
43
53
value = [
44
54
value . slice ( 0 , prefix . length ) ,
45
55
_pad ( '' , diff , '0' , true ) ,
46
56
value . slice ( prefix . length )
47
57
] . join ( '' )
58
+ } else {
59
+ value = _pad ( value , minWidth , padChar , leftJustify )
48
60
}
49
61
}
50
62
return value
51
63
}
52
64
53
- var _formatBaseX = function ( value , base , prefix , leftJustify , minWidth , precision , zeroPad ) {
65
+ var _formatBaseX = function ( value , base , leftJustify , minWidth , precision , padChar ) {
54
66
// Note: casts negative numbers to positive ones
55
67
var number = value >>> 0
56
- prefix = ( prefix && number && {
57
- '2' : '0b' ,
58
- '8' : '0' ,
59
- '16' : '0x'
60
- } [ base ] ) || ''
61
- value = prefix + _pad ( number . toString ( base ) , precision || 0 , '0' , false )
62
- return justify ( value , prefix , leftJustify , minWidth , zeroPad )
68
+ value = _pad ( number . toString ( base ) , precision || 0 , '0' , false )
69
+ return justify ( value , '' , leftJustify , minWidth , padChar )
63
70
}
64
71
65
72
// _formatString()
66
- var _formatString = function ( value , leftJustify , minWidth , precision , zeroPad , customPadChar ) {
73
+ var _formatString = function ( value , leftJustify , minWidth , precision , customPadChar ) {
67
74
if ( precision !== null && precision !== undefined ) {
68
75
value = value . slice ( 0 , precision )
69
76
}
70
- return justify ( value , '' , leftJustify , minWidth , zeroPad , customPadChar )
77
+ return justify ( value , '' , leftJustify , minWidth , customPadChar )
71
78
}
72
79
73
80
// doFormat()
74
- var doFormat = function ( substring , valueIndex , flags , minWidth , precision , type ) {
81
+ var doFormat = function ( substring , argIndex , modifiers , minWidth , precision , specifier ) {
75
82
var number , prefix , method , textTransform , value
76
83
77
84
if ( substring === '%%' ) {
78
85
return '%'
79
86
}
80
87
81
- // parse flags
88
+ // parse modifiers
89
+ var padChar = ' ' // pad with spaces by default
82
90
var leftJustify = false
83
- var positivePrefix = ''
84
- var zeroPad = false
85
- var prefixBaseX = false
86
- var customPadChar = ' '
87
- var flagsl = flags . length
88
- var j
89
- for ( j = 0 ; j < flagsl ; j ++ ) {
90
- switch ( flags . charAt ( j ) ) {
91
+ var positiveNumberPrefix = ''
92
+ var j , l
93
+
94
+ for ( j = 0 , l = modifiers . length ; j < l ; j ++ ) {
95
+ switch ( modifiers . charAt ( j ) ) {
91
96
case ' ' :
92
- positivePrefix = ' '
97
+ case '0' :
98
+ padChar = modifiers . charAt ( j )
93
99
break
94
100
case '+' :
95
- positivePrefix = '+'
101
+ positiveNumberPrefix = '+'
96
102
break
97
103
case '-' :
98
104
leftJustify = true
99
105
break
100
106
case "'" :
101
- customPadChar = flags . charAt ( j + 1 )
102
- break
103
- case '0' :
104
- zeroPad = true
105
- customPadChar = '0'
106
- break
107
- case '#' :
108
- prefixBaseX = true
107
+ if ( j + 1 < l ) {
108
+ padChar = modifiers . charAt ( j + 1 )
109
+ j ++
110
+ }
109
111
break
110
112
}
111
113
}
112
114
113
- // parameters may be null, undefined, empty-string or real valued
114
- // we want to ignore null, undefined and empty-string values
115
115
if ( ! minWidth ) {
116
116
minWidth = 0
117
- } else if ( minWidth === '*' ) {
118
- minWidth = + a [ i ++ ]
119
- } else if ( minWidth . charAt ( 0 ) === '*' ) {
120
- minWidth = + a [ minWidth . slice ( 1 , - 1 ) ]
121
117
} else {
122
118
minWidth = + minWidth
123
119
}
124
120
125
- // Note: undocumented perl feature:
126
- if ( minWidth < 0 ) {
127
- minWidth = - minWidth
128
- leftJustify = true
129
- }
130
-
131
121
if ( ! isFinite ( minWidth ) ) {
132
- throw new Error ( 'sprintf: (minimum-)width must be finite' )
122
+ throw new Error ( 'Width must be finite' )
133
123
}
134
124
135
125
if ( ! precision ) {
136
- precision = ' fFeE'. indexOf ( type ) > - 1 ? 6 : ( type === 'd' ) ? 0 : undefined
126
+ precision = ( specifier === 'd' ) ? 0 : ' fFeE'. indexOf ( specifier ) > - 1 ? 6 : undefined
137
127
} else {
138
128
precision = + precision
139
129
}
140
130
141
- // grab value using valueIndex if required?
142
- value = valueIndex ? a [ valueIndex . slice ( 0 , - 1 ) ] : a [ i ++ ]
131
+ if ( argIndex && + argIndex === 0 ) {
132
+ throw new Error ( 'Argument number must be greater than zero' )
133
+ }
134
+
135
+ if ( argIndex && + argIndex >= args . length ) {
136
+ throw new Error ( 'Too few arguments' )
137
+ }
143
138
144
- switch ( type ) {
139
+ value = argIndex ? args [ + argIndex ] : args [ i ++ ]
140
+
141
+ switch ( specifier ) {
142
+ case '%' :
143
+ return '%'
145
144
case 's' :
146
- return _formatString ( value + '' , leftJustify , minWidth , precision , zeroPad , customPadChar )
145
+ return _formatString ( value + '' , leftJustify , minWidth , precision , padChar )
147
146
case 'c' :
148
- return _formatString ( String . fromCharCode ( + value ) , leftJustify , minWidth , precision , zeroPad )
147
+ return _formatString ( String . fromCharCode ( + value ) , leftJustify , minWidth , precision , padChar )
149
148
case 'b' :
150
- return _formatBaseX ( value , 2 , prefixBaseX , leftJustify , minWidth , precision , zeroPad )
149
+ return _formatBaseX ( value , 2 , leftJustify , minWidth , precision , padChar )
151
150
case 'o' :
152
- return _formatBaseX ( value , 8 , prefixBaseX , leftJustify , minWidth , precision , zeroPad )
151
+ return _formatBaseX ( value , 8 , leftJustify , minWidth , precision , padChar )
153
152
case 'x' :
154
- return _formatBaseX ( value , 16 , prefixBaseX , leftJustify , minWidth , precision , zeroPad )
153
+ return _formatBaseX ( value , 16 , leftJustify , minWidth , precision , padChar )
155
154
case 'X' :
156
- return _formatBaseX ( value , 16 , prefixBaseX , leftJustify , minWidth , precision , zeroPad )
157
- . toUpperCase ( )
155
+ return _formatBaseX ( value , 16 , leftJustify , minWidth , precision , padChar )
156
+ . toUpperCase ( )
158
157
case 'u' :
159
- return _formatBaseX ( value , 10 , prefixBaseX , leftJustify , minWidth , precision , zeroPad )
158
+ return _formatBaseX ( value , 10 , leftJustify , minWidth , precision , padChar )
160
159
case 'i' :
161
160
case 'd' :
162
161
number = + value || 0
163
162
// Plain Math.round doesn't just truncate
164
163
number = Math . round ( number - number % 1 )
165
- prefix = number < 0 ? '-' : positivePrefix
164
+ prefix = number < 0 ? '-' : positiveNumberPrefix
166
165
value = prefix + _pad ( String ( Math . abs ( number ) ) , precision , '0' , false )
167
- return justify ( value , prefix , leftJustify , minWidth , zeroPad )
166
+
167
+ if ( leftJustify && padChar === '0' ) {
168
+ // can't right-pad 0s on integers
169
+ padChar = ' '
170
+ }
171
+ return justify ( value , prefix , leftJustify , minWidth , padChar )
168
172
case 'e' :
169
173
case 'E' :
170
174
case 'f' : // @todo : Should handle locales (as per setlocale)
171
175
case 'F' :
172
176
case 'g' :
173
177
case 'G' :
174
178
number = + value
175
- prefix = number < 0 ? '-' : positivePrefix
176
- method = [ 'toExponential' , 'toFixed' , 'toPrecision' ] [ 'efg' . indexOf ( type . toLowerCase ( ) ) ]
177
- textTransform = [ 'toString' , 'toUpperCase' ] [ 'eEfFgG' . indexOf ( type ) % 2 ]
179
+ prefix = number < 0 ? '-' : positiveNumberPrefix
180
+ method = [ 'toExponential' , 'toFixed' , 'toPrecision' ] [ 'efg' . indexOf ( specifier . toLowerCase ( ) ) ]
181
+ textTransform = [ 'toString' , 'toUpperCase' ] [ 'eEfFgG' . indexOf ( specifier ) % 2 ]
178
182
value = prefix + Math . abs ( number ) [ method ] ( precision )
179
- return justify ( value , prefix , leftJustify , minWidth , zeroPad ) [ textTransform ] ( )
183
+ return justify ( value , prefix , leftJustify , minWidth , padChar ) [ textTransform ] ( )
180
184
default :
181
- return substring
185
+ // unknown specifier, consume that char and return empty
186
+ return ''
182
187
}
183
188
}
184
189
185
- return format . replace ( regex , doFormat )
190
+ try {
191
+ return format . replace ( regex , doFormat )
192
+ } catch ( err ) {
193
+ return false
194
+ }
186
195
}
0 commit comments