@tannin/sprintf 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/types/index.d.ts +213 -56
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1,87 +1,244 @@
|
|
|
1
|
+
// ---- Base Types ----
|
|
2
|
+
|
|
1
3
|
type Specifiers = {
|
|
2
4
|
s: string;
|
|
3
5
|
d: number;
|
|
4
6
|
f: number;
|
|
5
7
|
};
|
|
8
|
+
|
|
6
9
|
type S = keyof Specifiers;
|
|
7
10
|
|
|
11
|
+
// ---- Tuple Math Utilities ----
|
|
12
|
+
|
|
13
|
+
// Builds a tuple of length L
|
|
8
14
|
type BuildTuple<L extends number, T extends any[] = []> = T['length'] extends L
|
|
9
15
|
? T
|
|
10
16
|
: BuildTuple<L, [any, ...T]>;
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
// Adds two numbers by building and merging tuples
|
|
19
|
+
type Add<A extends number, B extends number> = [
|
|
20
|
+
...BuildTuple<A>,
|
|
21
|
+
...BuildTuple<B>,
|
|
22
|
+
]['length'] extends infer R extends number
|
|
23
|
+
? R
|
|
14
24
|
: never;
|
|
15
25
|
|
|
26
|
+
// --- Escaped Percent Handling ----
|
|
27
|
+
|
|
28
|
+
// Removes escaped double-percent (%%) sequences from a format string
|
|
16
29
|
type StripEscapedPercents<T extends string> =
|
|
17
30
|
T extends `${infer Head}%%${infer Tail}`
|
|
18
31
|
? `${Head}${StripEscapedPercents<Tail>}`
|
|
19
32
|
: T;
|
|
20
33
|
|
|
21
|
-
|
|
22
|
-
StripEscapedPercents<T> extends `${any}%(${string})${S}${string}`
|
|
23
|
-
? true
|
|
24
|
-
: false;
|
|
34
|
+
// ---- Specifier Utilities ----
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
: false;
|
|
36
|
+
// Checks if a character is a valid format specifier
|
|
37
|
+
type IsValidSpec<C extends string> = C extends S ? C : never;
|
|
38
|
+
// ---- Format Specifier Parsing ----
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
// Parses precision and format specifier, supports dynamic precision (e.g. %.*f)
|
|
41
|
+
type ParsePrecisionAndSpec<T extends string> =
|
|
42
|
+
T extends `.*${infer RawSpec}${infer Rest}`
|
|
43
|
+
? IsValidSpec<RawSpec> extends infer Spec extends S
|
|
44
|
+
? ['.*', Spec, Rest]
|
|
45
|
+
: never
|
|
46
|
+
: T extends `0.${infer PrecisionAndSpec}${infer RestAfterDigits}`
|
|
47
|
+
? PrecisionAndSpec extends `${number}`
|
|
48
|
+
? RestAfterDigits extends `${infer RawSpec}${infer Rest}`
|
|
49
|
+
? IsValidSpec<RawSpec> extends infer Spec extends S
|
|
50
|
+
? [`.${PrecisionAndSpec}`, Spec, Rest]
|
|
51
|
+
: never
|
|
52
|
+
: never
|
|
53
|
+
: never
|
|
54
|
+
: T extends `.${infer PrecisionAndSpec}${infer RestAfterDigits}`
|
|
55
|
+
? PrecisionAndSpec extends `${number}`
|
|
56
|
+
? RestAfterDigits extends `${infer RawSpec}${infer Rest}`
|
|
57
|
+
? IsValidSpec<RawSpec> extends infer Spec extends S
|
|
58
|
+
? [`.${PrecisionAndSpec}`, Spec, Rest]
|
|
59
|
+
: never
|
|
60
|
+
: never
|
|
61
|
+
: never
|
|
62
|
+
: T extends `${infer RawSpec}${infer Rest}`
|
|
63
|
+
? IsValidSpec<RawSpec> extends infer Spec extends S
|
|
64
|
+
? ['', Spec, Rest]
|
|
65
|
+
: never
|
|
66
|
+
: never;
|
|
33
67
|
|
|
34
|
-
|
|
35
|
-
StripEscapedPercents<T> extends `${any}%.*${S}${any}` ? true : false;
|
|
68
|
+
// ---- Positional Placeholder Parsing ----
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
70
|
+
// Recursively collects all positional placeholders and their expected types
|
|
71
|
+
type CollectPositionalPlaceholders<
|
|
72
|
+
T extends string,
|
|
73
|
+
Collected extends Record<string, any> = {},
|
|
74
|
+
> =
|
|
75
|
+
StripEscapedPercents<T> extends `${infer _}%${infer PosStr}$${infer AfterPos}`
|
|
76
|
+
? PosStr extends `${number}`
|
|
77
|
+
? ParsePrecisionAndSpec<AfterPos> extends [
|
|
78
|
+
infer Precision extends string,
|
|
79
|
+
infer Spec extends S,
|
|
80
|
+
infer Rest extends string,
|
|
81
|
+
]
|
|
82
|
+
? IsValidSpec<Spec> extends never
|
|
83
|
+
? never
|
|
84
|
+
: Precision extends '.*'
|
|
85
|
+
? CollectPositionalPlaceholders<
|
|
86
|
+
Rest,
|
|
87
|
+
Collected & { [K in PosStr]: [number, Specifiers[Spec]] }
|
|
88
|
+
>
|
|
89
|
+
: CollectPositionalPlaceholders<
|
|
90
|
+
Rest,
|
|
91
|
+
Collected & { [K in PosStr]: Specifiers[Spec] }
|
|
92
|
+
>
|
|
93
|
+
: CollectPositionalPlaceholders<AfterPos, Collected>
|
|
94
|
+
: Collected
|
|
95
|
+
: Collected;
|
|
39
96
|
|
|
40
|
-
|
|
41
|
-
StripEscapedPercents<T> extends `${any}%(${infer Key})${infer Spec}${infer Rest}`
|
|
42
|
-
? Spec extends S
|
|
43
|
-
? { [K in Key]: Specifiers[Spec] } & ExtractNamedPlaceholders<Rest>
|
|
44
|
-
: never
|
|
45
|
-
: {};
|
|
97
|
+
// ---- Positional Argument Extraction ----
|
|
46
98
|
|
|
99
|
+
// Gets the max index used in positional placeholders
|
|
100
|
+
type GetMaxPosition<T extends Record<string, any>> = {
|
|
101
|
+
[K in keyof T]: K extends `${infer N extends number}` ? N : never;
|
|
102
|
+
}[keyof T];
|
|
103
|
+
|
|
104
|
+
// Computes max of a number by building up a tuple
|
|
105
|
+
type Max<N extends number, A extends any[] = []> = [N] extends [
|
|
106
|
+
Partial<A>['length'],
|
|
107
|
+
]
|
|
108
|
+
? A['length']
|
|
109
|
+
: Max<N, [0, ...A]>;
|
|
110
|
+
|
|
111
|
+
// Builds a final tuple of arguments from the collected positional types
|
|
112
|
+
type BuildPositionalTuple<
|
|
113
|
+
Collected extends { [K: number]: any },
|
|
114
|
+
MaxPos extends number,
|
|
115
|
+
CurrentPos extends number = 1,
|
|
116
|
+
Result extends any[] = [],
|
|
117
|
+
> =
|
|
118
|
+
CurrentPos extends Add<MaxPos, 1>
|
|
119
|
+
? Result
|
|
120
|
+
: `${CurrentPos}` extends keyof Collected
|
|
121
|
+
? Collected[CurrentPos] extends [any, any]
|
|
122
|
+
? BuildPositionalTuple<
|
|
123
|
+
Collected,
|
|
124
|
+
MaxPos,
|
|
125
|
+
Add<CurrentPos, 1>,
|
|
126
|
+
[...Result, ...Collected[CurrentPos]]
|
|
127
|
+
>
|
|
128
|
+
: BuildPositionalTuple<
|
|
129
|
+
Collected,
|
|
130
|
+
MaxPos,
|
|
131
|
+
Add<CurrentPos, 1>,
|
|
132
|
+
[...Result, Collected[CurrentPos]]
|
|
133
|
+
>
|
|
134
|
+
: BuildPositionalTuple<
|
|
135
|
+
Collected,
|
|
136
|
+
MaxPos,
|
|
137
|
+
Add<CurrentPos, 1>,
|
|
138
|
+
[...Result, unknown]
|
|
139
|
+
>;
|
|
140
|
+
|
|
141
|
+
// Main positional argument extractor
|
|
47
142
|
type ExtractPositionalPlaceholders<T extends string> =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
StripEscapedPercents<T> extends `${any}%.${infer Precision extends number}${infer Spec}${infer Rest}`
|
|
58
|
-
? Spec extends S
|
|
59
|
-
? [Specifiers[Spec], ...ExtractStaticPrecisionPlaceholders<Rest>]
|
|
60
|
-
: never
|
|
143
|
+
CollectPositionalPlaceholders<T> extends infer Collected extends Record<
|
|
144
|
+
string,
|
|
145
|
+
any
|
|
146
|
+
>
|
|
147
|
+
? keyof Collected extends never
|
|
148
|
+
? []
|
|
149
|
+
: Max<GetMaxPosition<Collected>> extends infer MaxPos extends number
|
|
150
|
+
? BuildPositionalTuple<Collected, MaxPos>
|
|
151
|
+
: []
|
|
61
152
|
: [];
|
|
62
153
|
|
|
63
|
-
|
|
64
|
-
StripEscapedPercents<T> extends `${any}%.*${infer Spec}${infer Rest}`
|
|
65
|
-
? Spec extends S
|
|
66
|
-
? [number, Specifiers[Spec], ...ExtractDynamicPrecisionPlaceholder<Rest>]
|
|
67
|
-
: never
|
|
68
|
-
: [];
|
|
154
|
+
// ---- Unnamed Placeholder Extraction ----
|
|
69
155
|
|
|
156
|
+
// Extracts unnamed placeholders like %s, %.2f, %.*d (ignores positional/named)
|
|
70
157
|
type ExtractUnnamedPlaceholders<T extends string> =
|
|
71
|
-
StripEscapedPercents<T> extends `${
|
|
72
|
-
?
|
|
73
|
-
?
|
|
74
|
-
:
|
|
158
|
+
StripEscapedPercents<T> extends `${infer _}%${infer AfterPercent}`
|
|
159
|
+
? AfterPercent extends `${infer _Num extends `${number}`}$${infer AfterPositional}`
|
|
160
|
+
? ExtractUnnamedPlaceholders<AfterPositional> // Skip positional
|
|
161
|
+
: ParsePrecisionAndSpec<AfterPercent> extends [
|
|
162
|
+
infer Precision extends string,
|
|
163
|
+
infer Spec extends S,
|
|
164
|
+
infer Rest extends string,
|
|
165
|
+
]
|
|
166
|
+
? [
|
|
167
|
+
...(Precision extends '.*'
|
|
168
|
+
? [number, Specifiers[Spec]]
|
|
169
|
+
: [Specifiers[Spec]]),
|
|
170
|
+
...ExtractUnnamedPlaceholders<Rest>,
|
|
171
|
+
]
|
|
172
|
+
: []
|
|
75
173
|
: [];
|
|
76
174
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
175
|
+
// ---- Named Placeholder Extraction ----
|
|
176
|
+
|
|
177
|
+
// Extracts named placeholders like %(key)s, %(name).2f
|
|
178
|
+
type ExtractNamedPlaceholders<T extends string> =
|
|
179
|
+
StripEscapedPercents<T> extends `${infer _}%(${infer Key})${infer AfterKey}`
|
|
180
|
+
? ParsePrecisionAndSpec<AfterKey> extends [
|
|
181
|
+
infer Precision extends string,
|
|
182
|
+
infer Spec extends S,
|
|
183
|
+
infer Rest extends string,
|
|
184
|
+
]
|
|
185
|
+
? Precision extends '.*'
|
|
186
|
+
? never // Optional: disallow dynamic precision for named
|
|
187
|
+
: { [K in Key]: Specifiers[Spec] } & ExtractNamedPlaceholders<Rest>
|
|
188
|
+
: {}
|
|
189
|
+
: {};
|
|
190
|
+
|
|
191
|
+
// ---- Format Type Guards ----
|
|
192
|
+
|
|
193
|
+
// Checks if string has named placeholders
|
|
194
|
+
type HasNamedPlaceholders<T extends string> =
|
|
195
|
+
StripEscapedPercents<T> extends `${infer _}%(${string})${string}`
|
|
196
|
+
? true
|
|
197
|
+
: false;
|
|
198
|
+
|
|
199
|
+
// Checks if string has positional placeholders
|
|
200
|
+
type HasPositionalPlaceholders<T extends string> =
|
|
201
|
+
StripEscapedPercents<T> extends `${infer _Before}%${infer Rest}`
|
|
202
|
+
? Rest extends `${infer Index}$${infer _After}`
|
|
203
|
+
? Index extends `${number}`
|
|
204
|
+
? true
|
|
205
|
+
: HasPositionalPlaceholders<Rest>
|
|
206
|
+
: HasPositionalPlaceholders<Rest>
|
|
207
|
+
: false;
|
|
208
|
+
|
|
209
|
+
// Checks if string has unnamed placeholders
|
|
210
|
+
type HasUnnamedPlaceholders<T extends string> =
|
|
211
|
+
StripEscapedPercents<T> extends `${infer _}%${infer Body}`
|
|
212
|
+
? Body extends `(${string})${string}` // Named — skip
|
|
213
|
+
? HasUnnamedPlaceholders<Body>
|
|
214
|
+
: Body extends `${number}$${string}` // Positional — skip
|
|
215
|
+
? HasUnnamedPlaceholders<Body>
|
|
216
|
+
: ParsePrecisionAndSpec<Body> extends [
|
|
217
|
+
infer _,
|
|
218
|
+
infer Spec extends S,
|
|
219
|
+
infer _,
|
|
220
|
+
]
|
|
221
|
+
? true
|
|
222
|
+
: HasUnnamedPlaceholders<Body>
|
|
223
|
+
: false;
|
|
224
|
+
|
|
225
|
+
// ---- Public API ----
|
|
226
|
+
|
|
227
|
+
// Extracts the argument types required for a sprintf-like string
|
|
228
|
+
export type SprintfArgs<T extends string> =
|
|
229
|
+
HasNamedPlaceholders<T> extends true
|
|
230
|
+
? HasPositionalPlaceholders<T> extends true
|
|
231
|
+
? [never] // Invalid: named + positional
|
|
232
|
+
: HasUnnamedPlaceholders<T> extends true
|
|
233
|
+
? [never] // Invalid: named + unnamed
|
|
234
|
+
: [values: ExtractNamedPlaceholders<T>]
|
|
235
|
+
: HasPositionalPlaceholders<T> extends true
|
|
236
|
+
? HasUnnamedPlaceholders<T> extends true
|
|
237
|
+
? [
|
|
238
|
+
...ExtractPositionalPlaceholders<T>,
|
|
239
|
+
...ExtractUnnamedPlaceholders<T>,
|
|
240
|
+
]
|
|
241
|
+
: ExtractPositionalPlaceholders<T>
|
|
242
|
+
: HasUnnamedPlaceholders<T> extends true
|
|
243
|
+
? ExtractUnnamedPlaceholders<T>
|
|
244
|
+
: [];
|