1 | // Limited implementation of python % string operator, supports only %s and %r for now
|
---|
2 | // (other formats are not used here, but may appear in custom templates)
|
---|
3 |
|
---|
4 | 'use strict'
|
---|
5 |
|
---|
6 | const { inspect } = require('util')
|
---|
7 |
|
---|
8 |
|
---|
9 | module.exports = function sub(pattern, ...values) {
|
---|
10 | let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g
|
---|
11 |
|
---|
12 | let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) {
|
---|
13 | if (is_literal) return '%'
|
---|
14 |
|
---|
15 | let padded_count = 0
|
---|
16 | if (is_padded) {
|
---|
17 | if (values.length === 0) throw new TypeError('not enough arguments for format string')
|
---|
18 | padded_count = values.shift()
|
---|
19 | if (!Number.isInteger(padded_count)) throw new TypeError('* wants int')
|
---|
20 | }
|
---|
21 |
|
---|
22 | let str
|
---|
23 | if (name !== undefined) {
|
---|
24 | let dict = values[0]
|
---|
25 | if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping')
|
---|
26 | if (!(name in dict)) throw new TypeError(`no such key: '${name}'`)
|
---|
27 | str = dict[name]
|
---|
28 | } else {
|
---|
29 | if (values.length === 0) throw new TypeError('not enough arguments for format string')
|
---|
30 | str = values.shift()
|
---|
31 | }
|
---|
32 |
|
---|
33 | switch (format) {
|
---|
34 | case 's':
|
---|
35 | str = String(str)
|
---|
36 | break
|
---|
37 | case 'r':
|
---|
38 | str = inspect(str)
|
---|
39 | break
|
---|
40 | case 'd':
|
---|
41 | case 'i':
|
---|
42 | if (typeof str !== 'number') {
|
---|
43 | throw new TypeError(`%${format} format: a number is required, not ${typeof str}`)
|
---|
44 | }
|
---|
45 | str = String(str.toFixed(0))
|
---|
46 | break
|
---|
47 | default:
|
---|
48 | throw new TypeError(`unsupported format character '${format}'`)
|
---|
49 | }
|
---|
50 |
|
---|
51 | if (padded_count > 0) {
|
---|
52 | return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count)
|
---|
53 | } else {
|
---|
54 | return str
|
---|
55 | }
|
---|
56 | })
|
---|
57 |
|
---|
58 | if (values.length) {
|
---|
59 | if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) {
|
---|
60 | // mapping
|
---|
61 | } else {
|
---|
62 | throw new TypeError('not all arguments converted during string formatting')
|
---|
63 | }
|
---|
64 | }
|
---|
65 |
|
---|
66 | return result
|
---|
67 | }
|
---|