mirror of
https://github.com/fmtlib/fmt.git
synced 2025-12-06 16:57:03 +08:00
Compare commits
716 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e871e39b | ||
|
|
e137db699a | ||
|
|
d6712ff2c0 | ||
|
|
2d839bbc61 | ||
|
|
5bc56e24a9 | ||
|
|
2727215c11 | ||
|
|
790b9389ae | ||
|
|
a731c73fd5 | ||
|
|
3391f9e992 | ||
|
|
9f197b22ae | ||
|
|
f20b16617e | ||
|
|
14451704d5 | ||
|
|
7a0da1c68a | ||
|
|
e3d2174b3c | ||
|
|
a6fb4d3b06 | ||
|
|
706fecea30 | ||
|
|
e00fd756cc | ||
|
|
fc17e825d9 | ||
|
|
3fccfb8a80 | ||
|
|
690c9c71a0 | ||
|
|
1122268510 | ||
|
|
a4c7e17133 | ||
|
|
bfd0129b91 | ||
|
|
62f57b2496 | ||
|
|
ef7a566413 | ||
|
|
42840cb415 | ||
|
|
5ac44cd128 | ||
|
|
23c13b3060 | ||
|
|
29c46fb82d | ||
|
|
a0571f3f59 | ||
|
|
a195dd6b37 | ||
|
|
33ad559eb8 | ||
|
|
b6cd356196 | ||
|
|
c3be070b7e | ||
|
|
27bf8b47fe | ||
|
|
407c905e45 | ||
|
|
f781d2b932 | ||
|
|
5987082c47 | ||
|
|
681c9e689b | ||
|
|
913507044b | ||
|
|
ff357e9e4a | ||
|
|
728dfeab5b | ||
|
|
7adc922ebb | ||
|
|
70ed0ab82a | ||
|
|
b95fd6132b | ||
|
|
7241bbb149 | ||
|
|
c0ddbacfd3 | ||
|
|
9395ef5fcb | ||
|
|
9721d974fc | ||
|
|
d6bdb69c62 | ||
|
|
08d38d6e78 | ||
|
|
a2289b8593 | ||
|
|
e8da5ba275 | ||
|
|
e2aa06cd0a | ||
|
|
85f6ecc7a0 | ||
|
|
378a5ab3c1 | ||
|
|
656d14db8b | ||
|
|
31bed1bb65 | ||
|
|
8eebb4334b | ||
|
|
beefc1c14f | ||
|
|
81fe170849 | ||
|
|
491dc16a6d | ||
|
|
41326207c7 | ||
|
|
c4f70ab69e | ||
|
|
5e214f0c43 | ||
|
|
1234bc312e | ||
|
|
b77a751625 | ||
|
|
8db24c0ea9 | ||
|
|
03c7e28965 | ||
|
|
47a18b2fe9 | ||
|
|
eb44d87b52 | ||
|
|
b580360ab7 | ||
|
|
486e7ba579 | ||
|
|
27ea09836a | ||
|
|
b5b9317a3c | ||
|
|
d13e5d048d | ||
|
|
b9ac8b225d | ||
|
|
4801f54e59 | ||
|
|
5f66e07cb0 | ||
|
|
17be91c079 | ||
|
|
dcea616535 | ||
|
|
a2fd48e039 | ||
|
|
e28c371c0f | ||
|
|
6b6cdd9405 | ||
|
|
c5e55972ae | ||
|
|
dc409ee86d | ||
|
|
4cce5f458d | ||
|
|
aa8a30838a | ||
|
|
b18ece7d71 | ||
|
|
e424e3f2e6 | ||
|
|
ddd20d57ec | ||
|
|
594143e374 | ||
|
|
09005428ab | ||
|
|
aaefc029d1 | ||
|
|
9fb9f17dac | ||
|
|
a3b3d7ed46 | ||
|
|
9aee518f3e | ||
|
|
83189189a1 | ||
|
|
9bb14ffc47 | ||
|
|
556c4177b6 | ||
|
|
bfdef8b15d | ||
|
|
e33c76a1c2 | ||
|
|
f9face7147 | ||
|
|
36390db094 | ||
|
|
da97ba2914 | ||
|
|
79a5e93027 | ||
|
|
18e160eb4c | ||
|
|
53d006abfd | ||
|
|
63bef33999 | ||
|
|
d7cbfef11c | ||
|
|
26b033e239 | ||
|
|
609188cb92 | ||
|
|
84b9c00711 | ||
|
|
9908d00037 | ||
|
|
0a94e06750 | ||
|
|
053d262944 | ||
|
|
e72e43af1c | ||
|
|
80549a630e | ||
|
|
f17b9aa44c | ||
|
|
882702c219 | ||
|
|
16d371b649 | ||
|
|
619b3a5aa0 | ||
|
|
79c7f8a70b | ||
|
|
20e0d6d8dd | ||
|
|
8ba99c0f05 | ||
|
|
656228fbee | ||
|
|
02fb76d69b | ||
|
|
a57f196cad | ||
|
|
a75e8af487 | ||
|
|
e129591f02 | ||
|
|
13d4f8469e | ||
|
|
e2f89e6d21 | ||
|
|
489fd7ca4b | ||
|
|
69324379f2 | ||
|
|
ff1caa58c9 | ||
|
|
e181e94140 | ||
|
|
43c88d00ae | ||
|
|
33a7d55c9b | ||
|
|
8e4676e4a5 | ||
|
|
b5c4d25cd1 | ||
|
|
a77efa4b4a | ||
|
|
e7e71009c7 | ||
|
|
b7b261977e | ||
|
|
61e0503daf | ||
|
|
72c82296d6 | ||
|
|
a402614e8c | ||
|
|
e719c43cc6 | ||
|
|
8a8ff6177c | ||
|
|
5b99b334f0 | ||
|
|
9588458917 | ||
|
|
f91dc80f4c | ||
|
|
4120581167 | ||
|
|
7ffc3ca15b | ||
|
|
e9ddc97b9a | ||
|
|
0d145936ec | ||
|
|
8f3a965186 | ||
|
|
ae8cb1e8e6 | ||
|
|
add164f6b3 | ||
|
|
23059d558e | ||
|
|
505cc3d0c2 | ||
|
|
b8a502615d | ||
|
|
814f51eab6 | ||
|
|
93f03953af | ||
|
|
35dcc58263 | ||
|
|
553ec11ec0 | ||
|
|
a0ecfe3e1c | ||
|
|
127413ddaa | ||
|
|
7d29ebe4af | ||
|
|
20c8fdad06 | ||
|
|
300ce75ca6 | ||
|
|
513f978241 | ||
|
|
6a3b40524c | ||
|
|
2fa3e1a1bb | ||
|
|
fc8d07cfe5 | ||
|
|
27c5aab349 | ||
|
|
bc0193535a | ||
|
|
353bd895a2 | ||
|
|
953cffa701 | ||
|
|
571c02d475 | ||
|
|
f4345467fc | ||
|
|
1ef8348070 | ||
|
|
a5dccffa56 | ||
|
|
4a149f513f | ||
|
|
067bc479b4 | ||
|
|
730fd4d9a7 | ||
|
|
5860688d7e | ||
|
|
46be88bc1e | ||
|
|
cc88914904 | ||
|
|
fc0c76a075 | ||
|
|
6332a38529 | ||
|
|
02de29e003 | ||
|
|
6d51c78c1e | ||
|
|
0f4e9d0bde | ||
|
|
d9d50495ac | ||
|
|
befbc5fdb8 | ||
|
|
8aa1d6a9fb | ||
|
|
6d79757a38 | ||
|
|
1ff0b7f5e1 | ||
|
|
ea985e84f8 | ||
|
|
f7033da09e | ||
|
|
b723c021df | ||
|
|
3ba3c390fb | ||
|
|
ab161a71c6 | ||
|
|
b5266fd3b9 | ||
|
|
9b0ebd4435 | ||
|
|
7af94e5597 | ||
|
|
2924fcf8f6 | ||
|
|
102752ad45 | ||
|
|
a6cd72c9eb | ||
|
|
07885271a1 | ||
|
|
4999416e5c | ||
|
|
55a8f6a4be | ||
|
|
eb9a95d426 | ||
|
|
d5c33e4f45 | ||
|
|
a2225f2887 | ||
|
|
b43b2f9537 | ||
|
|
1312b4a162 | ||
|
|
4404dc05dd | ||
|
|
7bb6fcb325 | ||
|
|
59259a5fde | ||
|
|
542ea7c40b | ||
|
|
40626af88b | ||
|
|
7fdd6846ba | ||
|
|
6caff7ed9c | ||
|
|
71a5483875 | ||
|
|
448929d491 | ||
|
|
26d87edab1 | ||
|
|
505ee058f7 | ||
|
|
ccab417195 | ||
|
|
ec1349d348 | ||
|
|
0ed2a65a8a | ||
|
|
e22c943070 | ||
|
|
b252bad3c6 | ||
|
|
2680831231 | ||
|
|
8978ab09b2 | ||
|
|
c936e2e44e | ||
|
|
a7d7b894cd | ||
|
|
e98155a6fb | ||
|
|
41b3bed4d2 | ||
|
|
67d9e49322 | ||
|
|
9db5e4df22 | ||
|
|
906eaf2ddb | ||
|
|
9f6c12c3dc | ||
|
|
2d0518b5f7 | ||
|
|
c81cbed2b7 | ||
|
|
c7925241c7 | ||
|
|
c709138359 | ||
|
|
db405954cd | ||
|
|
0a917ee2f5 | ||
|
|
969d4aef60 | ||
|
|
8061c7c8c4 | ||
|
|
7b59df4119 | ||
|
|
b8192d233a | ||
|
|
e814b5fabf | ||
|
|
ed0d216f7e | ||
|
|
bd9554a29e | ||
|
|
f086dc0d27 | ||
|
|
f10b6dd816 | ||
|
|
f470b9c566 | ||
|
|
b28214487d | ||
|
|
6d69f0c5f2 | ||
|
|
da776c9a66 | ||
|
|
64db979e38 | ||
|
|
5f2e61fdd5 | ||
|
|
b3d45e1d3f | ||
|
|
5f6fb96df1 | ||
|
|
5199e0f885 | ||
|
|
2f58430573 | ||
|
|
d5d32c1e81 | ||
|
|
204661287b | ||
|
|
e1ab383361 | ||
|
|
b9e0e94a01 | ||
|
|
a81842428d | ||
|
|
f53055efe0 | ||
|
|
b2dfcb2b80 | ||
|
|
7ac97cbd1d | ||
|
|
17898794a9 | ||
|
|
443a8ef342 | ||
|
|
3607e92dc9 | ||
|
|
43e31614cc | ||
|
|
989826ce50 | ||
|
|
9d6e24c64e | ||
|
|
0843317e08 | ||
|
|
784eac839d | ||
|
|
6fdf225a32 | ||
|
|
332da79bf3 | ||
|
|
7b273fbb54 | ||
|
|
191c504b10 | ||
|
|
d13fb6092f | ||
|
|
dd780fde44 | ||
|
|
37e6474718 | ||
|
|
77c0fc07d9 | ||
|
|
9212ff6ca1 | ||
|
|
864bdf9638 | ||
|
|
b776cf66fc | ||
|
|
bdbf957b9a | ||
|
|
577fd3be88 | ||
|
|
faac8b1fa9 | ||
|
|
123913715a | ||
|
|
8c1059b92e | ||
|
|
4e5aafbf43 | ||
|
|
db30fb3b81 | ||
|
|
3401ce2be2 | ||
|
|
7f7695524a | ||
|
|
251320fcb7 | ||
|
|
94ab51cb8c | ||
|
|
0ca42e836e | ||
|
|
ed27df5760 | ||
|
|
d42a068dbd | ||
|
|
f2cec917da | ||
|
|
d5b866e242 | ||
|
|
5676e408f5 | ||
|
|
71d24b564d | ||
|
|
c9267da4df | ||
|
|
373855c1b0 | ||
|
|
52eeeb52a6 | ||
|
|
9cf9f38ede | ||
|
|
4946bdb729 | ||
|
|
01a5b56f0d | ||
|
|
cb6fdf2191 | ||
|
|
f841ae61e2 | ||
|
|
a3d05d70ce | ||
|
|
41539c29f3 | ||
|
|
aabe63910c | ||
|
|
f90090be2c | ||
|
|
9ff9c695db | ||
|
|
06ad1224eb | ||
|
|
5f0572acdc | ||
|
|
898d438571 | ||
|
|
937b7c5c10 | ||
|
|
01914f0389 | ||
|
|
c43da35701 | ||
|
|
8303d140a1 | ||
|
|
b0b3dc5ff9 | ||
|
|
586ea06f02 | ||
|
|
5750f434fa | ||
|
|
bfbdc2be9a | ||
|
|
87e0072673 | ||
|
|
d57040f949 | ||
|
|
21aa0956d4 | ||
|
|
3f864a4505 | ||
|
|
093b39ca5e | ||
|
|
2c3a5698ef | ||
|
|
fc1b0f3486 | ||
|
|
1d066890c7 | ||
|
|
dad3237514 | ||
|
|
880e1494dc | ||
|
|
e3ddede6c4 | ||
|
|
e9ec4fdc88 | ||
|
|
feb72126b4 | ||
|
|
8d517e54c9 | ||
|
|
563fc74ae4 | ||
|
|
3e04222d53 | ||
|
|
853df39d0a | ||
|
|
11742a09c7 | ||
|
|
da24fac101 | ||
|
|
5fa4bdd758 | ||
|
|
3c8aad8df7 | ||
|
|
0e8aad961d | ||
|
|
debe784aab | ||
|
|
f6d1125676 | ||
|
|
73d0d3f75d | ||
|
|
08f60f1efc | ||
|
|
faf3f84085 | ||
|
|
f3a41441df | ||
|
|
3f33cb21d2 | ||
|
|
b07a90386e | ||
|
|
a6fba51773 | ||
|
|
25e2929988 | ||
|
|
00ab2e98b5 | ||
|
|
a3ef285aec | ||
|
|
28d1abc9d2 | ||
|
|
90704b9efd | ||
|
|
86dae01c23 | ||
|
|
d8a79eafdc | ||
|
|
7c3d0152e5 | ||
|
|
7c50da5385 | ||
|
|
873670ba3f | ||
|
|
735d4cc05e | ||
|
|
141380172f | ||
|
|
4302d74293 | ||
|
|
0f51ea79d3 | ||
|
|
9600fee020 | ||
|
|
47a66c5ecc | ||
|
|
385c01dc7b | ||
|
|
df249d8ad3 | ||
|
|
dfad80d1c5 | ||
|
|
536cabd562 | ||
|
|
b1a054706e | ||
|
|
bfd95392c7 | ||
|
|
9ced61bca4 | ||
|
|
75e5be6adc | ||
|
|
a169d7fa46 | ||
|
|
a6c45dfea8 | ||
|
|
a35389b3c2 | ||
|
|
5a3576acc8 | ||
|
|
542600013f | ||
|
|
720da57bab | ||
|
|
680db66c3a | ||
|
|
56ce41ef63 | ||
|
|
cf50e4d6a4 | ||
|
|
6580d7b808 | ||
|
|
7e73566ce7 | ||
|
|
8523dba2dc | ||
|
|
e3d3b24fc1 | ||
|
|
1521bba701 | ||
|
|
00649552a8 | ||
|
|
4b8e2838f0 | ||
|
|
7d4662f7ab | ||
|
|
27110bc474 | ||
|
|
68f3153762 | ||
|
|
168df9a064 | ||
|
|
4daa3d591f | ||
|
|
e9eaa27e5a | ||
|
|
2b6a786e35 | ||
|
|
a16ff5787b | ||
|
|
601be1cbe7 | ||
|
|
58c185b634 | ||
|
|
a0a9ba2afc | ||
|
|
cc2ba8f9ed | ||
|
|
a18d42b208 | ||
|
|
4046f97278 | ||
|
|
6bdc12a199 | ||
|
|
786a4b0968 | ||
|
|
2cb3b7c64b | ||
|
|
e9cba69057 | ||
|
|
02537548f3 | ||
|
|
c68c5fa7c7 | ||
|
|
22701d5f63 | ||
|
|
e62c41ffb0 | ||
|
|
18792893d8 | ||
|
|
c90bc91862 | ||
|
|
c95722ad62 | ||
|
|
db06b0df87 | ||
|
|
b9ec48d9ca | ||
|
|
3faf6f181e | ||
|
|
d64b100a30 | ||
|
|
ff9ee0461a | ||
|
|
1c5883bef0 | ||
|
|
cacc3108c5 | ||
|
|
fade652ade | ||
|
|
96dca569a1 | ||
|
|
891c9a73ae | ||
|
|
9282222b7d | ||
|
|
e5b20ff0d0 | ||
|
|
ff92223549 | ||
|
|
80c4d42c66 | ||
|
|
3b70966df5 | ||
|
|
05226c4bd9 | ||
|
|
c283b458a5 | ||
|
|
fe79932c26 | ||
|
|
23fcf1942a | ||
|
|
3f296e3d4a | ||
|
|
a197a994c5 | ||
|
|
6d43c755bc | ||
|
|
1f87b1c58d | ||
|
|
ed8f8be70d | ||
|
|
55a0a9cd62 | ||
|
|
5c926d9ff9 | ||
|
|
8b024662d4 | ||
|
|
2f1424be90 | ||
|
|
239aa6911b | ||
|
|
497df6db61 | ||
|
|
a25e594f6a | ||
|
|
503dff93ec | ||
|
|
3374a95b50 | ||
|
|
0e62e5dc7c | ||
|
|
7ce013971b | ||
|
|
07e70151d5 | ||
|
|
4197727712 | ||
|
|
527e98e3f8 | ||
|
|
8a19b2db77 | ||
|
|
e97df46ae1 | ||
|
|
39f1e0903a | ||
|
|
d832830f60 | ||
|
|
b329ff194f | ||
|
|
2af403ce64 | ||
|
|
b7513b1d00 | ||
|
|
761d35f763 | ||
|
|
545dc4148a | ||
|
|
3f5e45dd33 | ||
|
|
2e3b6fbd9f | ||
|
|
a0328e1f9f | ||
|
|
de28ef5f86 | ||
|
|
2d5e561a6b | ||
|
|
6537fb439c | ||
|
|
50aac2ac92 | ||
|
|
538d8777e5 | ||
|
|
0335312320 | ||
|
|
463fe65f17 | ||
|
|
1782a6eac0 | ||
|
|
b52fb98846 | ||
|
|
b6a6ec7f1c | ||
|
|
89999f1672 | ||
|
|
b90b4bc981 | ||
|
|
a1d6f9a973 | ||
|
|
689ec7a087 | ||
|
|
28143dc99d | ||
|
|
1bde49e545 | ||
|
|
f924d16e47 | ||
|
|
ab8f9d5b08 | ||
|
|
6f62db098a | ||
|
|
ab44ee7521 | ||
|
|
0d4e7e3fee | ||
|
|
8ee89546ff | ||
|
|
a5deb96bf5 | ||
|
|
61a241f03f | ||
|
|
ff82d8d2b5 | ||
|
|
0cc20f5639 | ||
|
|
2ba6785d8f | ||
|
|
5644e7507c | ||
|
|
5345cfe6b3 | ||
|
|
3e9fdb3a1f | ||
|
|
3ada4aed20 | ||
|
|
b37be85bf1 | ||
|
|
70643b2511 | ||
|
|
967e2d177d | ||
|
|
02c5d637c5 | ||
|
|
047bf75c24 | ||
|
|
2d3ba32e79 | ||
|
|
6c90b31fbd | ||
|
|
9408c2ae8c | ||
|
|
cc3ff1529d | ||
|
|
158893b384 | ||
|
|
f5a16a484b | ||
|
|
cad876be4c | ||
|
|
debf6f8285 | ||
|
|
35f4fab4c4 | ||
|
|
ff8f324786 | ||
|
|
bd48715d81 | ||
|
|
57d6df62f7 | ||
|
|
8ed4a9dcc1 | ||
|
|
f288f45e46 | ||
|
|
5bf577ca58 | ||
|
|
b6de66819e | ||
|
|
6870e4b06b | ||
|
|
5cdef76034 | ||
|
|
a2c290bc34 | ||
|
|
f1e3016c13 | ||
|
|
106dc8fd64 | ||
|
|
c3344e21e2 | ||
|
|
5f438c967e | ||
|
|
2a257798d4 | ||
|
|
22d50c1a9c | ||
|
|
1cc10ab68f | ||
|
|
6aaf7f4b79 | ||
|
|
b4d1d7f8e6 | ||
|
|
1e0771c70a | ||
|
|
3df47a4677 | ||
|
|
b4aea98b55 | ||
|
|
565461a0d3 | ||
|
|
e2b7238707 | ||
|
|
1e0c6cdc3b | ||
|
|
a8bcf81f72 | ||
|
|
15694c9a84 | ||
|
|
4cae2da0d0 | ||
|
|
79e5ae919c | ||
|
|
894b71da85 | ||
|
|
7a6a2a79ed | ||
|
|
387395fc7c | ||
|
|
6a88415499 | ||
|
|
9a2aae37d4 | ||
|
|
8803768363 | ||
|
|
4fa533c70e | ||
|
|
d980dd7171 | ||
|
|
4eed488c66 | ||
|
|
a6ecd25b80 | ||
|
|
9f29345ea0 | ||
|
|
4986b4c0ef | ||
|
|
a5f4d9820c | ||
|
|
bc3af51272 | ||
|
|
60740b7c24 | ||
|
|
9ef160d309 | ||
|
|
0b80978c27 | ||
|
|
4f39d88650 | ||
|
|
a86b1acf6a | ||
|
|
c9ef07bc4e | ||
|
|
8c4cfab57a | ||
|
|
7e3aa6d982 | ||
|
|
7c66216008 | ||
|
|
1416edabbb | ||
|
|
d4aeca9922 | ||
|
|
eee93ddffa | ||
|
|
b310a0d48b | ||
|
|
985c3399d1 | ||
|
|
4a55b0d5fd | ||
|
|
64a6c84592 | ||
|
|
66920feeee | ||
|
|
f4dad85c3a | ||
|
|
db4becabed | ||
|
|
fec2cc7af1 | ||
|
|
621e9c17c5 | ||
|
|
bca7040556 | ||
|
|
8c4b17fe64 | ||
|
|
516a2e2049 | ||
|
|
6797f0c39a | ||
|
|
db496b47c1 | ||
|
|
8eda3c8e90 | ||
|
|
53316903e6 | ||
|
|
8a484ad577 | ||
|
|
b446cc9e67 | ||
|
|
0204dd359d | ||
|
|
d8876b7787 | ||
|
|
c0fab5e2f7 | ||
|
|
64313e915c | ||
|
|
8e3da9da2c | ||
|
|
2a2f73f7c1 | ||
|
|
6dd9194abd | ||
|
|
a017bba062 | ||
|
|
5eb023cd56 | ||
|
|
f213d83306 | ||
|
|
b3ccc2d210 | ||
|
|
7477dda28d | ||
|
|
e582d377c2 | ||
|
|
cd8d01d8cd | ||
|
|
377cf203e3 | ||
|
|
5a0a37340c | ||
|
|
bbf8b3bd01 | ||
|
|
a3f3f2ec9a | ||
|
|
e3676ca309 | ||
|
|
0379bf3a5d | ||
|
|
c59ee969f3 | ||
|
|
1a79bbfa83 | ||
|
|
89af1ad77d | ||
|
|
0e741e0daa | ||
|
|
d1acc667c1 | ||
|
|
4fb7008c90 | ||
|
|
589898e28b | ||
|
|
62382e3650 | ||
|
|
94b8bc8eae | ||
|
|
020af729dd | ||
|
|
fb07b37c5b | ||
|
|
3135421257 | ||
|
|
993f56cff6 | ||
|
|
c6c830e203 | ||
|
|
b906c321f0 | ||
|
|
f8c0c8ee78 | ||
|
|
c71d03fcb0 | ||
|
|
50a8c3e9bf | ||
|
|
98314319ad | ||
|
|
0ce49aeb4a | ||
|
|
bf870ae3d1 | ||
|
|
c98518351e | ||
|
|
9f0c0c468b | ||
|
|
9f269062a7 | ||
|
|
15f939c3de | ||
|
|
928a07bb04 | ||
|
|
7891699737 | ||
|
|
58aba5a3de | ||
|
|
5ee14d3508 | ||
|
|
b9c0e4dd82 | ||
|
|
8445327c84 | ||
|
|
8a06cee826 | ||
|
|
1db2274966 | ||
|
|
34ead4b39e | ||
|
|
3bf26009e4 | ||
|
|
d326c7298a | ||
|
|
6e462b89aa | ||
|
|
aff640c32f | ||
|
|
e23fb6a8b4 | ||
|
|
16b3542f7e | ||
|
|
29d7e58059 | ||
|
|
919f7c5e7f | ||
|
|
a80d668a52 | ||
|
|
707d7d923a | ||
|
|
de6ed8df8b | ||
|
|
ffdc3fdbd9 | ||
|
|
0c02813791 | ||
|
|
f8581bcecf | ||
|
|
31b3c325f6 | ||
|
|
52b32081f9 | ||
|
|
0b0b09f401 | ||
|
|
4173a6315a | ||
|
|
4239dfe081 | ||
|
|
ba36a04811 | ||
|
|
f6b4a23b83 | ||
|
|
42d3d703b5 | ||
|
|
9fcd9c4c12 | ||
|
|
7f157dca0a | ||
|
|
524ca1c715 | ||
|
|
bdc45eef76 | ||
|
|
439b6d7212 | ||
|
|
3cc32fdc8b | ||
|
|
0c9fce2ffe | ||
|
|
b47d662e71 | ||
|
|
e84297f255 | ||
|
|
0ad234ad13 | ||
|
|
de684ef776 | ||
|
|
447c6cbf44 | ||
|
|
bc8d32e964 | ||
|
|
0f87d6ffa6 | ||
|
|
808ea0191a | ||
|
|
55e76e6c20 | ||
|
|
8757f1f8d6 | ||
|
|
9228f349a5 | ||
|
|
e10643add2 | ||
|
|
f29a7e7970 | ||
|
|
f97deb0d7d | ||
|
|
3541353512 | ||
|
|
5ef93a9f80 | ||
|
|
c9102619da | ||
|
|
58d792b6d3 | ||
|
|
25adca5666 | ||
|
|
1408f1824d | ||
|
|
3fe4641d3a | ||
|
|
33e7ed1eb5 | ||
|
|
6a192f8d34 | ||
|
|
92cdbbae06 | ||
|
|
13038f37e8 | ||
|
|
6725225750 | ||
|
|
e60ff504ea | ||
|
|
ccea338070 | ||
|
|
92227c77a4 | ||
|
|
486838f26f | ||
|
|
a43391199f | ||
|
|
7a8b54a0ef |
@ -6,3 +6,10 @@ IndentPPDirectives: AfterHash
|
|||||||
IndentCaseLabels: false
|
IndentCaseLabels: false
|
||||||
AlwaysBreakTemplateDeclarations: false
|
AlwaysBreakTemplateDeclarations: false
|
||||||
DerivePointerAlignment: false
|
DerivePointerAlignment: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
QualifierAlignment: Left
|
||||||
|
AlignConsecutiveShortCaseStatements:
|
||||||
|
Enabled: true
|
||||||
|
AcrossEmptyLines: true
|
||||||
|
AcrossComments: true
|
||||||
|
AlignCaseColons: false
|
||||||
4
.clang-tidy
Normal file
4
.clang-tidy
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Checks: modernize-use-trailing-return-type
|
||||||
|
CheckOptions:
|
||||||
|
- key: modernize-use-trailing-return-type.TransformLambdas
|
||||||
|
value: none
|
||||||
8
.github/workflows/cifuzz.yml
vendored
8
.github/workflows/cifuzz.yml
vendored
@ -10,20 +10,22 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Build fuzzers
|
- name: Build fuzzers
|
||||||
id: build
|
id: build
|
||||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master
|
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a
|
||||||
with:
|
with:
|
||||||
oss-fuzz-project-name: 'fmt'
|
oss-fuzz-project-name: 'fmt'
|
||||||
dry-run: false
|
dry-run: false
|
||||||
language: c++
|
language: c++
|
||||||
|
|
||||||
- name: Run fuzzers
|
- name: Run fuzzers
|
||||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master
|
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a
|
||||||
with:
|
with:
|
||||||
oss-fuzz-project-name: 'fmt'
|
oss-fuzz-project-name: 'fmt'
|
||||||
fuzz-seconds: 300
|
fuzz-seconds: 300
|
||||||
dry-run: false
|
dry-run: false
|
||||||
language: c++
|
language: c++
|
||||||
|
|
||||||
- name: Upload crash
|
- name: Upload crash
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
if: failure() && steps.build.outcome == 'success'
|
if: failure() && steps.build.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
|
|||||||
7
.github/workflows/doc.yml
vendored
7
.github/workflows/doc.yml
vendored
@ -7,11 +7,10 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
|
runs-on: ubuntu-22.04
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Add Ubuntu mirrors
|
- name: Add Ubuntu mirrors
|
||||||
run: |
|
run: |
|
||||||
@ -25,7 +24,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install doxygen
|
sudo apt install doxygen
|
||||||
pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1
|
pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
|
||||||
cmake -E make_directory ${{runner.workspace}}/build
|
cmake -E make_directory ${{runner.workspace}}/build
|
||||||
# Workaround https://github.com/actions/checkout/issues/13:
|
# Workaround https://github.com/actions/checkout/issues/13:
|
||||||
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
|
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
|
||||||
|
|||||||
43
.github/workflows/lint.yml
vendored
43
.github/workflows/lint.yml
vendored
@ -8,48 +8,21 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
format_code:
|
format_code:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Install clang-format
|
- name: Install clang-format
|
||||||
uses: aminya/setup-cpp@290824452986e378826155f3379d31bce8753d76 # v0.37.0
|
run: |
|
||||||
with:
|
wget https://apt.llvm.org/llvm.sh
|
||||||
clangformat: 17.0.5
|
sudo bash ./llvm.sh 21
|
||||||
|
sudo apt install clang-format-21
|
||||||
|
|
||||||
- name: Run clang-format
|
- name: Run clang-format
|
||||||
id: clang_format
|
|
||||||
run: |
|
run: |
|
||||||
find include src -name '*.h' -o -name '*.cc' | xargs clang-format -i -style=file -fallback-style=none
|
find include src -name '*.h' -o -name '*.cc' | \
|
||||||
git diff | tee fmt.patch
|
xargs clang-format-21 -i -style=file -fallback-style=none
|
||||||
if [ -s fmt.patch ]; then
|
git diff --exit-code
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
||||||
if: failure() && steps.clang_format.outcome == 'failure'
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const patch = fs.readFileSync('fmt.patch', { encoding: 'utf8' });
|
|
||||||
const comment = `clang-format 17.0.5 found issues in the formatting in your code:
|
|
||||||
<details>
|
|
||||||
<summary>
|
|
||||||
View the diff from clang-format:
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
\`\`\`diff
|
|
||||||
${patch}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
`;
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
body: comment
|
|
||||||
});
|
|
||||||
|
|||||||
150
.github/workflows/linux.yml
vendored
150
.github/workflows/linux.yml
vendored
@ -7,85 +7,143 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
cxx: [g++-4.8, g++-10, clang++-9]
|
cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11]
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug, Release]
|
||||||
std: [11]
|
std: [11]
|
||||||
|
shared: [""]
|
||||||
include:
|
include:
|
||||||
- cxx: g++-4.8
|
- cxx: g++-4.9
|
||||||
install: sudo apt install g++-4.8
|
- cxx: clang++-3.6
|
||||||
- cxx: g++-8
|
- cxx: g++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 14
|
std: 14
|
||||||
install: sudo apt install g++-8
|
install: sudo apt install g++-11
|
||||||
- cxx: g++-8
|
- cxx: g++-11
|
||||||
build_type: Debug
|
|
||||||
std: 17
|
|
||||||
install: sudo apt install g++-8
|
|
||||||
- cxx: g++-9
|
|
||||||
build_type: Debug
|
|
||||||
std: 17
|
|
||||||
- cxx: g++-10
|
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 17
|
std: 17
|
||||||
- cxx: g++-11
|
- cxx: g++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 20
|
std: 20
|
||||||
install: sudo apt install g++-11
|
install: sudo apt install g++-11
|
||||||
- cxx: clang++-8
|
- cxx: g++-13
|
||||||
|
build_type: Release
|
||||||
|
std: 23
|
||||||
|
install: sudo apt install g++-13
|
||||||
|
shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
- cxx: clang++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 17
|
std: 17
|
||||||
cxxflags: -stdlib=libc++
|
cxxflags: -stdlib=libc++
|
||||||
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
|
install: sudo apt install clang-11 libc++-11-dev libc++abi-11-dev
|
||||||
- cxx: clang++-9
|
- cxx: clang++-11
|
||||||
install: sudo apt install clang-9
|
install: sudo apt install clang-11
|
||||||
- cxx: clang++-9
|
- cxx: clang++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||||
std: 17
|
std: 17
|
||||||
install: sudo apt install clang-9
|
install: sudo apt install clang-11
|
||||||
- cxx: clang++-11
|
- cxx: clang++-14
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 20
|
std: 20
|
||||||
- cxx: clang++-11
|
- cxx: clang++-14
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 20
|
std: 20
|
||||||
cxxflags: -stdlib=libc++
|
cxxflags: -stdlib=libc++
|
||||||
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
install: sudo apt install libc++-14-dev libc++abi-14-dev
|
||||||
- shared: -DBUILD_SHARED_LIBS=ON
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set timezone
|
- name: Set timezone
|
||||||
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
|
run: sudo timedatectl set-timezone 'Europe/Kyiv'
|
||||||
|
|
||||||
- name: Add repositories for older GCC
|
- name: Install GCC 4.9
|
||||||
run: |
|
run: |
|
||||||
# Below two repos provide GCC 4.8, 5.5 and 6.4
|
sudo apt update
|
||||||
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main'
|
sudo apt install libatomic1 libc6-dev libgomp1 libitm1 libmpc3
|
||||||
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe'
|
# https://launchpad.net/ubuntu/xenial/amd64/g++-4.9/4.9.3-13ubuntu2
|
||||||
# Below two repos additionally update GCC 6 to 6.5
|
wget --no-verbose \
|
||||||
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main'
|
http://launchpadlibrarian.net/230069137/libmpfr4_3.1.3-2_amd64.deb \
|
||||||
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe'
|
http://launchpadlibrarian.net/253728424/libasan1_4.9.3-13ubuntu2_amd64.deb \
|
||||||
if: ${{ matrix.cxx == 'g++-4.8' }}
|
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/253728426/libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/253728432/libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/253728314/gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/253728399/cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/253728404/gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/253728401/g++-4.9_4.9.3-13ubuntu2_amd64.deb
|
||||||
|
sudo dpkg -i \
|
||||||
|
libmpfr4_3.1.3-2_amd64.deb \
|
||||||
|
libasan1_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||||
|
g++-4.9_4.9.3-13ubuntu2_amd64.deb
|
||||||
|
if: ${{ matrix.cxx == 'g++-4.9' }}
|
||||||
|
|
||||||
|
- name: Install Clang 3.6
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libtinfo5
|
||||||
|
# https://code.launchpad.net/ubuntu/xenial/amd64/clang-3.6/1:3.6.2-3ubuntu2
|
||||||
|
wget --no-verbose \
|
||||||
|
http://launchpadlibrarian.net/230019046/libffi6_3.2.1-4_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346109/libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346128/libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346113/libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346131/libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445346022/libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/254405108/libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/254405097/libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/254405101/libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
http://launchpadlibrarian.net/254405091/clang-3.6_3.6.2-3ubuntu2_amd64.deb
|
||||||
|
sudo dpkg -i \
|
||||||
|
libffi6_3.2.1-4_amd64.deb \
|
||||||
|
libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
|
||||||
|
libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
|
||||||
|
libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
|
||||||
|
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||||
|
clang-3.6_3.6.2-3ubuntu2_amd64.deb
|
||||||
|
if: ${{ matrix.cxx == 'clang++-3.6' }}
|
||||||
|
|
||||||
- name: Add repositories for newer GCC
|
- name: Add repositories for newer GCC
|
||||||
run: |
|
run: |
|
||||||
sudo apt-add-repository ppa:ubuntu-toolchain-r/test
|
sudo apt-add-repository ppa:ubuntu-toolchain-r/test
|
||||||
if: ${{ matrix.cxx == 'g++-11' }}
|
if: ${{ matrix.cxx == 'g++-13' }}
|
||||||
|
|
||||||
- name: Add Ubuntu mirrors
|
- name: Add Ubuntu mirrors
|
||||||
run: |
|
run: |
|
||||||
# Github Actions caching proxy is at times unreliable
|
# GitHub Actions caching proxy is at times unreliable
|
||||||
# see https://github.com/actions/runner-images/issues/7048
|
# see https://github.com/actions/runner-images/issues/7048.
|
||||||
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
|
mirrors=/etc/apt/mirrors.txt
|
||||||
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
|
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | \
|
||||||
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
|
sudo tee $mirrors
|
||||||
|
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append $mirrors
|
||||||
|
sudo sed -i \
|
||||||
|
"s~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:$mirrors~" \
|
||||||
|
/etc/apt/sources.list
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create build environment
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
${{matrix.install}}
|
${{matrix.install}}
|
||||||
@ -98,10 +156,12 @@ jobs:
|
|||||||
CXX: ${{matrix.cxx}}
|
CXX: ${{matrix.cxx}}
|
||||||
CXXFLAGS: ${{matrix.cxxflags}}
|
CXXFLAGS: ${{matrix.cxxflags}}
|
||||||
run: |
|
run: |
|
||||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
|
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
|
||||||
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||||
|
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \
|
||||||
|
${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
|||||||
17
.github/workflows/macos.yml
vendored
17
.github/workflows/macos.yml
vendored
@ -9,22 +9,23 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-13, macos-14]
|
os: [macos-14]
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug, Release]
|
||||||
std: [11, 17, 20]
|
std: [11, 17, 20, 23]
|
||||||
exclude:
|
shared: [""]
|
||||||
- { os: macos-13, std: 11 }
|
|
||||||
- { os: macos-13, std: 17 }
|
|
||||||
include:
|
include:
|
||||||
- shared: -DBUILD_SHARED_LIBS=ON
|
- os: macos-14
|
||||||
|
std: 23
|
||||||
|
build_type: Release
|
||||||
|
shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
runs-on: '${{ matrix.os }}'
|
runs-on: '${{ matrix.os }}'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set timezone
|
- name: Set timezone
|
||||||
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
|
run: sudo systemsetup -settimezone 'Europe/Minsk'
|
||||||
|
|
||||||
- name: Select Xcode 14.3 (macOS 13)
|
- name: Select Xcode 14.3 (macOS 13)
|
||||||
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"
|
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"
|
||||||
|
|||||||
8
.github/workflows/scorecard.yml
vendored
8
.github/workflows/scorecard.yml
vendored
@ -29,12 +29,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
|
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
@ -52,7 +52,7 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
@ -60,6 +60,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
23
.github/workflows/windows.yml
vendored
23
.github/workflows/windows.yml
vendored
@ -10,27 +10,18 @@ jobs:
|
|||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# windows-2019 has MSVC 2019 installed;
|
|
||||||
# windows-2022 has MSVC 2022 installed:
|
# windows-2022 has MSVC 2022 installed:
|
||||||
# https://github.com/actions/virtual-environments.
|
# https://github.com/actions/virtual-environments.
|
||||||
os: [windows-2019]
|
os: [windows-2022]
|
||||||
platform: [Win32, x64]
|
platform: [Win32, x64]
|
||||||
toolset: [v140, v141, v142]
|
toolset: [v142]
|
||||||
standard: [14, 17, 20]
|
standard: [14, 17, 20]
|
||||||
shared: ["", -DBUILD_SHARED_LIBS=ON]
|
shared: ["", -DBUILD_SHARED_LIBS=ON]
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug, Release]
|
||||||
exclude:
|
exclude:
|
||||||
- { toolset: v140, standard: 17 }
|
|
||||||
- { toolset: v140, standard: 20 }
|
|
||||||
- { toolset: v141, standard: 14 }
|
|
||||||
- { toolset: v141, standard: 20 }
|
|
||||||
- { toolset: v142, standard: 14 }
|
- { toolset: v142, standard: 14 }
|
||||||
- { platform: Win32, toolset: v140 }
|
|
||||||
- { platform: Win32, toolset: v141 }
|
|
||||||
- { platform: Win32, standard: 14 }
|
- { platform: Win32, standard: 14 }
|
||||||
- { platform: Win32, standard: 20 }
|
- { platform: Win32, standard: 20 }
|
||||||
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
|
|
||||||
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
|
|
||||||
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
|
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
|
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
include:
|
include:
|
||||||
@ -41,10 +32,10 @@ jobs:
|
|||||||
standard: 20
|
standard: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set timezone
|
- name: Set timezone
|
||||||
run: tzutil /s "Ekaterinburg Standard Time"
|
run: tzutil /s "FLE Standard Time"
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
@ -81,14 +72,14 @@ jobs:
|
|||||||
sys: [ mingw64, ucrt64 ]
|
sys: [ mingw64, ucrt64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Set timezone
|
- name: Set timezone
|
||||||
run: tzutil /s "Ekaterinburg Standard Time"
|
run: tzutil /s "FLE Standard Time"
|
||||||
shell: cmd
|
shell: cmd
|
||||||
- uses: msys2/setup-msys2@d0e80f58dffbc64f6a3a1f43527d469b4fc7b6c8 # v2.23.0
|
- uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638 # v2.28.0
|
||||||
with:
|
with:
|
||||||
release: false
|
release: false
|
||||||
msystem: ${{matrix.sys}}
|
msystem: ${{matrix.sys}}
|
||||||
pacboy: cc:p cmake:p ninja:p lld:p
|
pacboy: cc:p cmake:p ninja:p lld:p
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||||
env: { LDFLAGS: -fuse-ld=lld }
|
env: { LDFLAGS: -fuse-ld=lld }
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,11 +3,14 @@
|
|||||||
*.xcodeproj
|
*.xcodeproj
|
||||||
*~
|
*~
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.cache/
|
||||||
|
.vs/
|
||||||
/CMakeScripts
|
/CMakeScripts
|
||||||
/Testing
|
/Testing
|
||||||
/_CPack_Packages
|
/_CPack_Packages
|
||||||
/install_manifest.txt
|
/install_manifest.txt
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
|
CMakeUserPresets.json
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
CPack*.cmake
|
CPack*.cmake
|
||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
|
|||||||
@ -9,7 +9,9 @@ endif ()
|
|||||||
# or if it is the master project.
|
# or if it is the master project.
|
||||||
if (NOT DEFINED FMT_MASTER_PROJECT)
|
if (NOT DEFINED FMT_MASTER_PROJECT)
|
||||||
set(FMT_MASTER_PROJECT OFF)
|
set(FMT_MASTER_PROJECT OFF)
|
||||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
# NOTE: source vs current_source detection is unreliable
|
||||||
|
# this heuristic is more generally applicable esp w.r.t FetchContent
|
||||||
|
if (NOT DEFINED PROJECT_NAME)
|
||||||
set(FMT_MASTER_PROJECT ON)
|
set(FMT_MASTER_PROJECT ON)
|
||||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||||
endif ()
|
endif ()
|
||||||
@ -27,12 +29,15 @@ endfunction()
|
|||||||
# DEPRECATED! Should be merged into add_module_library.
|
# DEPRECATED! Should be merged into add_module_library.
|
||||||
function(enable_module target)
|
function(enable_module target)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
|
||||||
target_compile_options(${target}
|
set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
PRIVATE /interface /ifcOutput ${BMI}
|
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
|
||||||
INTERFACE /reference fmt=${BMI})
|
target_compile_options(${target}
|
||||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
PRIVATE /interface /ifcOutput ${BMI}
|
||||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
INTERFACE /reference fmt=${BMI})
|
||||||
|
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||||
|
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||||
|
endif()
|
||||||
endif ()
|
endif ()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
@ -69,8 +74,6 @@ function(add_module_library name)
|
|||||||
target_compile_options(${name} PUBLIC -fmodules-ts)
|
target_compile_options(${name} PUBLIC -fmodules-ts)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_compile_definitions(${name} PRIVATE FMT_MODULE)
|
|
||||||
|
|
||||||
if (FMT_USE_CMAKE_MODULES)
|
if (FMT_USE_CMAKE_MODULES)
|
||||||
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
|
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
|
||||||
FILES ${sources})
|
FILES ${sources})
|
||||||
@ -160,7 +163,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
|||||||
|
|
||||||
# Options that control generation of various targets.
|
# Options that control generation of various targets.
|
||||||
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_INSTALL "Generate the install target." ON)
|
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||||
@ -201,8 +204,7 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
|||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
|
||||||
|
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
include(JoinPaths)
|
include(JoinPaths)
|
||||||
@ -240,7 +242,13 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
|||||||
endif ()
|
endif ()
|
||||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||||
-Wnull-dereference -Wduplicated-cond)
|
-Wduplicated-cond)
|
||||||
|
# Workaround for GCC regression
|
||||||
|
# [12/13/14/15 regression] New (since gcc 12) false positive null-dereference in vector.resize
|
||||||
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108860
|
||||||
|
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnull-dereference)
|
||||||
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
set(WERROR_FLAG -Werror)
|
set(WERROR_FLAG -Werror)
|
||||||
endif ()
|
endif ()
|
||||||
@ -289,6 +297,7 @@ function(add_headers VAR)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# Define the fmt library, its includes and the needed defines.
|
# Define the fmt library, its includes and the needed defines.
|
||||||
|
set(FMT_HEADERS)
|
||||||
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
|
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
|
||||||
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
||||||
xchar.h)
|
xchar.h)
|
||||||
@ -319,7 +328,7 @@ else ()
|
|||||||
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
|
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE PUBLIC
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||||
|
|
||||||
@ -359,8 +368,8 @@ if (NOT MSVC)
|
|||||||
# Unicode is always supported on compilers other than MSVC.
|
# Unicode is always supported on compilers other than MSVC.
|
||||||
elseif (FMT_UNICODE)
|
elseif (FMT_UNICODE)
|
||||||
# Unicode support requires compiling with /utf-8.
|
# Unicode support requires compiling with /utf-8.
|
||||||
target_compile_options(fmt PUBLIC $<$<COMPILE_LANGUAGE:CXX>:/utf-8>)
|
target_compile_options(fmt PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
|
||||||
target_compile_options(fmt-header-only INTERFACE $<$<COMPILE_LANGUAGE:CXX>:/utf-8>)
|
target_compile_options(fmt-header-only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
|
||||||
else ()
|
else ()
|
||||||
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
|
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
|
||||||
endif ()
|
endif ()
|
||||||
@ -369,7 +378,7 @@ target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
|||||||
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
|
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
|
||||||
|
|
||||||
target_include_directories(fmt-header-only
|
target_include_directories(fmt-header-only
|
||||||
${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE INTERFACE
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||||
|
|
||||||
@ -420,7 +429,9 @@ if (FMT_INSTALL)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Install the library and headers.
|
# Install the library and headers.
|
||||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
install(TARGETS ${INSTALL_TARGETS}
|
||||||
|
COMPONENT fmt_core
|
||||||
|
EXPORT ${targets_export_name}
|
||||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||||
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
||||||
@ -433,13 +444,15 @@ if (FMT_INSTALL)
|
|||||||
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||||
|
|
||||||
# Install version, config and target files.
|
# Install version, config and target files.
|
||||||
install(
|
install(FILES ${project_config} ${version_config}
|
||||||
FILES ${project_config} ${version_config}
|
DESTINATION ${FMT_CMAKE_DIR}
|
||||||
DESTINATION ${FMT_CMAKE_DIR})
|
COMPONENT fmt_core)
|
||||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||||
NAMESPACE fmt::)
|
NAMESPACE fmt::
|
||||||
|
COMPONENT fmt_core)
|
||||||
|
|
||||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
|
||||||
|
COMPONENT fmt_core)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
function(add_doc_target)
|
function(add_doc_target)
|
||||||
@ -475,7 +488,8 @@ function(add_doc_target)
|
|||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
|
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
|
||||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL)
|
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
|
||||||
|
COMPONENT fmt_doc OPTIONAL)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
if (FMT_DOC)
|
if (FMT_DOC)
|
||||||
|
|||||||
701
ChangeLog.md
701
ChangeLog.md
@ -1,3 +1,698 @@
|
|||||||
|
# 12.1.0 - 2025-10-29
|
||||||
|
|
||||||
|
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
|
||||||
|
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
|
||||||
|
|
||||||
|
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
|
||||||
|
gcc (https://github.com/fmtlib/fmt/issues/4573).
|
||||||
|
|
||||||
|
- Made `std::variant` and `std::expected` formatters work with `format_as`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4574,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
|
||||||
|
|
||||||
|
- Made `fmt::join<string_view>` work with C++ modules
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4379,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
|
||||||
|
|
||||||
|
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
|
||||||
|
|
||||||
|
- Fixed a compatibility issue with C++ modules in clang
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
|
||||||
|
|
||||||
|
- Added support for cv-qualified types to the `std::optional` formatter
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4561,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
|
||||||
|
|
||||||
|
- Added demangling support (used in exception and `std::type_info` formatters)
|
||||||
|
for libc++ and clang-cl
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4542,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4560,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4568,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4571).
|
||||||
|
Thanks @FatihBAKIR and @rohitsutreja.
|
||||||
|
|
||||||
|
- Switched to global `malloc`/`free` to enable allocator customization
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4569,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
|
||||||
|
|
||||||
|
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
|
||||||
|
|
||||||
|
- Fixed compilation with locales disabled in the header-only mode
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4550).
|
||||||
|
|
||||||
|
- Fixed compilation with clang 21 and `-std=c++20`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4552).
|
||||||
|
|
||||||
|
- Fixed a dynamic linking issue with clang-cl
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4576,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
|
||||||
|
|
||||||
|
- Fixed a warning suppression leakage on gcc
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
|
||||||
|
|
||||||
|
- Made more internal color APIs `constexpr`
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
|
||||||
|
|
||||||
|
- Fixed compatibility with clang as a host compiler for NVCC
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
|
||||||
|
|
||||||
|
- Fixed various warnings and lint issues
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4565,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4572,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4557).
|
||||||
|
Thanks @LiangHuDream and @teruyamato0731.
|
||||||
|
|
||||||
|
- Improved documentation
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4549,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4551,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4566,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4567,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4578,).
|
||||||
|
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
|
||||||
|
|
||||||
|
# 12.0.0 - 2025-09-17
|
||||||
|
|
||||||
|
- Optimized the default floating point formatting
|
||||||
|
(https://github.com/fmtlib/fmt/issues/3675,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4516). In particular, formatting a
|
||||||
|
`double` with format string compilation into a stack allocated buffer is
|
||||||
|
more than 60% faster in version 12.0 compared to 11.2 according to
|
||||||
|
[dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark):
|
||||||
|
|
||||||
|
```
|
||||||
|
Function Time (ns) Speedup
|
||||||
|
fmt11 34.471 1.00x
|
||||||
|
fmt12 21.000 1.64x
|
||||||
|
```
|
||||||
|
|
||||||
|
<img width="766" height="609" src="https://github.com/user-attachments/assets/d7d768ad-7543-468c-b0bb-449abf73b31b" />
|
||||||
|
|
||||||
|
- Added `constexpr` support to `fmt::format`. For example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <fmt/compile.h>
|
||||||
|
|
||||||
|
using namespace fmt::literals;
|
||||||
|
std::string s = fmt::format(""_cf, 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
now works at compile time provided that `std::string` supports `constexpr`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/3403,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4456). Thanks @msvetkin.
|
||||||
|
|
||||||
|
- Added `FMT_STATIC_FORMAT` that allows formatting into a string of the exact
|
||||||
|
required size at compile time.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <fmt/compile.h>
|
||||||
|
|
||||||
|
constexpr auto s = FMT_STATIC_FORMAT("{}", 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
compiles to just
|
||||||
|
|
||||||
|
```s
|
||||||
|
__ZL1s:
|
||||||
|
.asciiz "42"
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be accessed as a C string with `s.c_str()` or as a string view with
|
||||||
|
`s.str()`.
|
||||||
|
|
||||||
|
- Improved C++20 module support
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4451,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4459,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4476,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4488,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4491,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4495).
|
||||||
|
Thanks @arBmind, @tkhyn, @Mishura4, @anonymouspc and @autoantwort.
|
||||||
|
|
||||||
|
- Switched to using estimated display width in precision. For example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
fmt::print("|{:.4}|\n|1234|\n", "🐱🐱🐱");
|
||||||
|
```
|
||||||
|
|
||||||
|
prints
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
because `🐱` has an estimated width of 2
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4272,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4443,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4475).
|
||||||
|
Thanks @nikhilreddydev and @localspook.
|
||||||
|
|
||||||
|
- Fix interaction between debug presentation, precision, and width for strings
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4478). Thanks @localspook.
|
||||||
|
|
||||||
|
- Implemented allocator propagation on `basic_memory_buffer` move
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4487,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4490). Thanks @toprakmurat.
|
||||||
|
|
||||||
|
- Fixed an ambiguity between `std::reference_wrapper<T>` and `format_as`
|
||||||
|
formatters (https://github.com/fmtlib/fmt/issues/4424,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4434). Thanks @jeremy-rifkin.
|
||||||
|
|
||||||
|
- Removed the following deprecated APIs:
|
||||||
|
|
||||||
|
- `has_formatter`: use `is_formattable` instead,
|
||||||
|
- `basic_format_args::parse_context_type`,
|
||||||
|
`basic_format_args::formatter_type` and similar aliases in context types,
|
||||||
|
- wide stream overload of `fmt::printf`,
|
||||||
|
- wide stream overloads of `fmt::print` that take text styles,
|
||||||
|
- `is_*char` traits,
|
||||||
|
- `fmt::localtime`.
|
||||||
|
|
||||||
|
- Deprecated wide overloads of `fmt::fprintf` and `fmt::sprintf`.
|
||||||
|
|
||||||
|
- Improved diagnostics for the incorrect usage of `fmt::ptr`
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4453). Thanks @TobiSchluter.
|
||||||
|
|
||||||
|
- Made handling of ANSI escape sequences more efficient
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4511,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4528).
|
||||||
|
Thanks @localspook and @Anas-Hamdane.
|
||||||
|
|
||||||
|
- Fixed a buffer overflow on all emphasis flags set
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4498). Thanks @dominicpoeschko.
|
||||||
|
|
||||||
|
- Fixed an integer overflow for precision close to the max `int` value.
|
||||||
|
|
||||||
|
- Fixed compatibility with WASI (https://github.com/fmtlib/fmt/issues/4496,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4497). Thanks @whitequark.
|
||||||
|
|
||||||
|
- Fixed `back_insert_iterator` detection, preventing a fallback on slower path
|
||||||
|
that handles arbitrary iterators (https://github.com/fmtlib/fmt/issues/4454).
|
||||||
|
|
||||||
|
- Fixed handling of invalid glibc `FILE` buffers
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4469).
|
||||||
|
|
||||||
|
- Added `wchar_t` support to the `std::byte` formatter
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4479,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4480). Thanks @phprus.
|
||||||
|
|
||||||
|
- Changed component prefix from `fmt-` to `fmt_` for compatibility with
|
||||||
|
NSIS/CPack on Windows, e.g. `fmt-doc` changed to `fmt_doc`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4441,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4442). Thanks @n-stein.
|
||||||
|
|
||||||
|
- Added the `FMT_CUSTOM_ASSERT_FAIL` macro to simplify providing a custom
|
||||||
|
`fmt::assert_fail` implementation (https://github.com/fmtlib/fmt/pull/4505).
|
||||||
|
Thanks @HazardyKnusperkeks.
|
||||||
|
|
||||||
|
- Switched to `FMT_THROW` on reporting format errors so that it can be
|
||||||
|
overriden by users when exceptions are disabled
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4521). Thanks @HazardyKnusperkeks.
|
||||||
|
|
||||||
|
- Improved master project detection and disabled install targets when using
|
||||||
|
{fmt} as a subproject by default (https://github.com/fmtlib/fmt/pull/4536).
|
||||||
|
Thanks @crueter.
|
||||||
|
|
||||||
|
- Made various code improvements
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4445,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4448,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4473,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4522).
|
||||||
|
Thanks @localspook, @tchaikov and @way4sahil.
|
||||||
|
|
||||||
|
- Added Conan instructions to the docs
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4537). Thanks @uilianries.
|
||||||
|
|
||||||
|
- Removed Bazel files to avoid issues with downstream packaging
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4530). Thanks @mering.
|
||||||
|
|
||||||
|
- Added more entries for generated files to `.gitignore`
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4355,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4512).
|
||||||
|
Thanks @dinomight and @localspook.
|
||||||
|
|
||||||
|
- Fixed various warnings and compilation issues
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4447,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4470,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4474,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4477,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4471,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4483,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4515,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4533,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4534).
|
||||||
|
Thanks @dodomorandi, @localspook, @remyjette, @Tomek-Stolarczyk, @Mishura4,
|
||||||
|
@mattiasljungstrom and @FatihBAKIR.
|
||||||
|
|
||||||
|
# 11.2.0 - 2025-05-03
|
||||||
|
|
||||||
|
- Added the `s` specifier for `std::error_code`. It allows formatting an error
|
||||||
|
message as a string. For example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <fmt/std.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto ec = std::make_error_code(std::errc::no_such_file_or_directory);
|
||||||
|
fmt::print("{:s}\n", ec);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
prints
|
||||||
|
|
||||||
|
```
|
||||||
|
No such file or directory
|
||||||
|
```
|
||||||
|
(The actual message is platform-specific.)
|
||||||
|
|
||||||
|
- Fixed formatting of `std::chrono::local_time` and `tm`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/3815,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4350).
|
||||||
|
For example ([godbolt](https://www.godbolt.org/z/8o4b1PPn5)):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::chrono::zoned_time zt(
|
||||||
|
std::chrono::current_zone(),
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
fmt::print("{}", zt.get_local_time());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
is now formatted consistenly across platforms.
|
||||||
|
|
||||||
|
- Added diagnostics for cases when timezone information is not available.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
fmt::print("{:Z}", std::chrono::local_seconds());
|
||||||
|
```
|
||||||
|
|
||||||
|
now gives a compile-time error.
|
||||||
|
|
||||||
|
- Deprecated `fmt::localtime` in favor of `std::localtime`.
|
||||||
|
|
||||||
|
- Fixed compilation with GCC 15 and C++20 modules enabled
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4347). Thanks @tkhyn.
|
||||||
|
|
||||||
|
- Fixed handling of named arguments in format specs
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4360,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight.
|
||||||
|
|
||||||
|
- Added error reporting for duplicate named arguments
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4282,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight.
|
||||||
|
|
||||||
|
- Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4375,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4394).
|
||||||
|
|
||||||
|
- Optimized `text_style` using bit packing
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4363). Thanks @localspook.
|
||||||
|
|
||||||
|
- Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4383). Thanks @localspook.
|
||||||
|
|
||||||
|
- Fixed a flush issue in `fmt::print` when using libstdc++
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4398).
|
||||||
|
|
||||||
|
- Fixed `fmt::println` usage with `FMT_ENFORCE_COMPILE_STRING` and legacy
|
||||||
|
compile-time checks (https://github.com/fmtlib/fmt/pull/4407).
|
||||||
|
Thanks @madmaxoft.
|
||||||
|
|
||||||
|
- Removed legacy header `fmt/core.h` from docs
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4421,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4422). Thanks @krzysztofkortas.
|
||||||
|
|
||||||
|
- Worked around limitations of `__builtin_strlen` during constant evaluation
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4423,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4429). Thanks @BRevzin.
|
||||||
|
|
||||||
|
- Worked around a bug in MSVC v141 (https://github.com/fmtlib/fmt/issues/4412,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4413). Thanks @hirohira9119.
|
||||||
|
|
||||||
|
- Removed the `fmt_detail` namespace
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4324).
|
||||||
|
|
||||||
|
- Removed specializations of `std::is_floating_point` in tests
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4417).
|
||||||
|
|
||||||
|
- Fixed a CMake error when setting `CMAKE_MODULE_PATH` in the pedantic mode
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4426). Thanks @rlalik.
|
||||||
|
|
||||||
|
- Updated the Bazel config (https://github.com/fmtlib/fmt/pull/4400).
|
||||||
|
Thanks @Vertexwahn.
|
||||||
|
|
||||||
|
# 11.1.4 - 2025-02-26
|
||||||
|
|
||||||
|
- Fixed ABI compatibility with earlier 11.x versions on Windows
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4359).
|
||||||
|
|
||||||
|
- Improved the logic of switching between fixed and exponential format for
|
||||||
|
`float` (https://github.com/fmtlib/fmt/issues/3649).
|
||||||
|
|
||||||
|
- Moved `is_compiled_string` to the public API
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4335,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
|
||||||
|
|
||||||
|
- Simplified implementation of `operator""_cf`
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4349). Thanks @localspook.
|
||||||
|
|
||||||
|
- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329).
|
||||||
|
Thanks @localspook.
|
||||||
|
|
||||||
|
- Fixed handling of BMI paths with the Ninja generator
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn.
|
||||||
|
|
||||||
|
- Fixed gcc 8.3 compile errors (https://github.com/fmtlib/fmt/issues/4331,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4336). Thanks @sergiud.
|
||||||
|
|
||||||
|
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/pull/4356).
|
||||||
|
Thanks @dinomight.
|
||||||
|
|
||||||
|
# 11.1.3 - 2025-01-25
|
||||||
|
|
||||||
|
- Fixed compilation on GCC 9.4 (https://github.com/fmtlib/fmt/issues/4313).
|
||||||
|
|
||||||
|
- Worked around an internal compiler error when using C++20 modules with GCC
|
||||||
|
14.2 and earlier (https://github.com/fmtlib/fmt/issues/4295).
|
||||||
|
|
||||||
|
- Worked around a bug in GCC 6 (https://github.com/fmtlib/fmt/issues/4318).
|
||||||
|
|
||||||
|
- Fixed an issue caused by instantiating `formatter<const T>`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4303,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4325). Thanks @timsong-cpp.
|
||||||
|
|
||||||
|
- Fixed formatting into `std::ostreambuf_iterator` when using format string
|
||||||
|
compilation (https://github.com/fmtlib/fmt/issues/4309,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4312). Thanks @phprus.
|
||||||
|
|
||||||
|
- Restored a constraint on the map formatter so that it correctly reports as
|
||||||
|
unformattable when the element is (https://github.com/fmtlib/fmt/pull/4326).
|
||||||
|
Thanks @timsong-cpp.
|
||||||
|
|
||||||
|
- Reduced the size of format specs (https://github.com/fmtlib/fmt/issues/4298).
|
||||||
|
|
||||||
|
- Readded `args()` to `fmt::format_context`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4307,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4310). Thanks @Erroneous1.
|
||||||
|
|
||||||
|
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/issues/4314,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4322). Thanks @ZehMatt.
|
||||||
|
|
||||||
|
- Fixed a pedantic mode error in the CMake config
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4327). Thanks @rlalik.
|
||||||
|
|
||||||
|
# 11.1.2 - 2025-01-12
|
||||||
|
|
||||||
|
- Fixed ABI compatibility with earlier 11.x versions
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4292).
|
||||||
|
|
||||||
|
- Added `wchar_t` support to the `std::bitset` formatter
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4285,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4286,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4289,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4290). Thanks @phprus.
|
||||||
|
|
||||||
|
- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via
|
||||||
|
`add_subdirectory` (https://github.com/fmtlib/fmt/issues/4283).
|
||||||
|
|
||||||
|
- Updated docs for meson (https://github.com/fmtlib/fmt/pull/4291).
|
||||||
|
Thanks @trim21.
|
||||||
|
|
||||||
|
- Fixed a compilation error in chrono on nvcc
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4297,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4301). Thanks @breyerml.
|
||||||
|
|
||||||
|
- Fixed various warnings
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4288,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4299). Thanks @GamesTrap and @edo9300.
|
||||||
|
|
||||||
|
# 11.1.1 - 2024-12-27
|
||||||
|
|
||||||
|
- Fixed ABI compatibility with earlier 11.x versions
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4278).
|
||||||
|
|
||||||
|
- Defined CMake components (`core` and `doc`) to allow docs to be installed
|
||||||
|
separately (https://github.com/fmtlib/fmt/pull/4276).
|
||||||
|
Thanks @carlsmedstad.
|
||||||
|
|
||||||
|
# 11.1.0 - 2024-12-25
|
||||||
|
|
||||||
|
- Improved C++20 module support
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4081,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4083,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4084,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4152,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4153,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4169,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4190,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4234,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4239).
|
||||||
|
Thanks @kamrann and @Arghnews.
|
||||||
|
|
||||||
|
- Reduced debug (unoptimized) binary code size and the number of template
|
||||||
|
instantiations when passing formatting arguments. For example, unoptimized
|
||||||
|
binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and
|
||||||
|
~60% on clang (x86-64).
|
||||||
|
|
||||||
|
GCC:
|
||||||
|
- Before: 161 instructions of which 105 are in reusable functions
|
||||||
|
([godbolt](https://www.godbolt.org/z/s9bGoo4ze)).
|
||||||
|
- After: 116 instructions of which 60 are in reusable functions
|
||||||
|
([godbolt](https://www.godbolt.org/z/r7GGGxMs6)).
|
||||||
|
|
||||||
|
Clang:
|
||||||
|
- Before: 310 instructions of which 251 are in reusable functions
|
||||||
|
([godbolt](https://www.godbolt.org/z/Ts88b7M9o)).
|
||||||
|
- After: 194 instructions of which 135 are in reusable functions
|
||||||
|
([godbolt](https://www.godbolt.org/z/vcrjP8ceW)).
|
||||||
|
|
||||||
|
- Added an experimental `fmt::writer` API that can be used for writing to
|
||||||
|
different destinations such as files or strings
|
||||||
|
(https://github.com/fmtlib/fmt/issues/2354).
|
||||||
|
For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <fmt/os.h>
|
||||||
|
|
||||||
|
void write_text(fmt::writer w) {
|
||||||
|
w.print("The answer is {}.", 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Write to FILE.
|
||||||
|
write_text(stdout);
|
||||||
|
|
||||||
|
// Write to fmt::ostream.
|
||||||
|
auto f = fmt::output_file("myfile");
|
||||||
|
write_text(f);
|
||||||
|
|
||||||
|
// Write to std::string.
|
||||||
|
auto sb = fmt::string_buffer();
|
||||||
|
write_text(sb);
|
||||||
|
std::string s = sb.str();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Added width and alignment support to the formatter of `std::error_code`.
|
||||||
|
|
||||||
|
- Made `std::expected<void, E>` formattable
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4145,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4148).
|
||||||
|
For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
fmt::print("{}", std::expected<void, int>());
|
||||||
|
```
|
||||||
|
|
||||||
|
prints
|
||||||
|
|
||||||
|
```
|
||||||
|
expected()
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks @phprus.
|
||||||
|
|
||||||
|
- Made `fmt::is_formattable<void>` SFINAE-friendly
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4147).
|
||||||
|
|
||||||
|
- Added support for `_BitInt` formatting when using clang
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4007,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4072,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4140,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4173,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4176).
|
||||||
|
For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
using int42 = _BitInt(42);
|
||||||
|
fmt::print("{}", int42(100));
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks @Arghnews.
|
||||||
|
|
||||||
|
- Added the `n` specifier for tuples and pairs
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc.
|
||||||
|
|
||||||
|
- Added support for tuple-like types to `fmt::join`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4226,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus.
|
||||||
|
|
||||||
|
- Made more types formattable at compile time
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH.
|
||||||
|
|
||||||
|
- Implemented a more efficient compile-time `fmt::formatted_size`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4102,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus.
|
||||||
|
|
||||||
|
- Fixed compile-time formatting of some string types
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd.
|
||||||
|
|
||||||
|
- Made compiled version of `fmt::format_to` work with
|
||||||
|
`std::back_insert_iterator<std::vector<char>>`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4206,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus.
|
||||||
|
|
||||||
|
- Added a formatter for `std::reference_wrapper`
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4163,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus.
|
||||||
|
|
||||||
|
- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j`
|
||||||
|
and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH.
|
||||||
|
|
||||||
|
- Made microseconds formatted as `us` instead of `µs` if the Unicode support is
|
||||||
|
disabled (https://github.com/fmtlib/fmt/issues/4088).
|
||||||
|
|
||||||
|
- Fixed an unreleased regression in transcoding of surrogate pairs
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4094,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus.
|
||||||
|
|
||||||
|
- Made `fmt::appender` satisfy `std::output_iterator` concept
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4092,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus.
|
||||||
|
|
||||||
|
- Made `std::iterator_traits<fmt::appender>` standard-conforming
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter.
|
||||||
|
|
||||||
|
- Made it easier to reuse `fmt::formatter<std::string_view>` for types with
|
||||||
|
an implicit conversion to `std::string_view`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4036,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
|
||||||
|
|
||||||
|
- Made it possible to disable `<filesystem>` use via `FMT_CPP_LIB_FILESYSTEM`
|
||||||
|
for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4257,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4258,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus.
|
||||||
|
|
||||||
|
- Fixed compatibility with platforms that use 80-bit `long double`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4245,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma.
|
||||||
|
|
||||||
|
- Added support for UTF-32 code units greater than `0xFFFF` in fill
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4201).
|
||||||
|
|
||||||
|
- Fixed handling of legacy encodings on Windows with GCC
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4162).
|
||||||
|
|
||||||
|
- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4261,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel.
|
||||||
|
|
||||||
|
- Added `fmt::dynamic_format_arg_store::size`
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch.
|
||||||
|
|
||||||
|
- Removed the ability to control locale usage via an undocumented
|
||||||
|
`FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`.
|
||||||
|
|
||||||
|
- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other
|
||||||
|
similar macros.
|
||||||
|
|
||||||
|
- Improved include directory ordering to reduce the chance of including
|
||||||
|
incorrect headers when using multiple versions of {fmt}
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan.
|
||||||
|
|
||||||
|
- Made it possible to compile a subset of {fmt} without the C++ runtime.
|
||||||
|
|
||||||
|
- Improved documentation and README
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4066,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4117,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4203,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh.
|
||||||
|
|
||||||
|
- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado.
|
||||||
|
|
||||||
|
- Improved CI (https://github.com/fmtlib/fmt/pull/4155,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus.
|
||||||
|
|
||||||
|
- Fixed various warnings and compilation issues
|
||||||
|
(https://github.com/fmtlib/fmt/issues/2708,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4091,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4109,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4113,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4125,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4129,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4130,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4131,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4132,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4133,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4144,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4150,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4158,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4159,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4160,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4170,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4177,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4187,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4188,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4194,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4200,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4205,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4207,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4208,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4210,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4220,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4231,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4232,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4233,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4236,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4267,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4271).
|
||||||
|
Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega,
|
||||||
|
@c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao.
|
||||||
|
|
||||||
|
# 11.0.2 - 2024-07-20
|
||||||
|
|
||||||
|
- Fixed compatibility with non-POSIX systems
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4054,
|
||||||
|
https://github.com/fmtlib/fmt/issues/4060).
|
||||||
|
|
||||||
|
- Fixed performance regressions when using `std::back_insert_iterator` with
|
||||||
|
`fmt::format_to` (https://github.com/fmtlib/fmt/issues/4070).
|
||||||
|
|
||||||
|
- Fixed handling of `std::generator` and move-only iterators
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4053,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4057). Thanks @Arghnews.
|
||||||
|
|
||||||
|
- Made `formatter<std::string_view>::parse` work with types convertible to
|
||||||
|
`std::string_view` (https://github.com/fmtlib/fmt/issues/4036,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
|
||||||
|
|
||||||
|
- Made `volatile void*` formattable
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4049,
|
||||||
|
https://github.com/fmtlib/fmt/pull/4056). Thanks @Arghnews.
|
||||||
|
|
||||||
|
- Made `Glib::ustring` not be confused with `std::string`
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4052).
|
||||||
|
|
||||||
|
- Made `fmt::context` iterator compatible with STL algorithms that rely on
|
||||||
|
iterator category (https://github.com/fmtlib/fmt/issues/4079).
|
||||||
|
|
||||||
# 11.0.1 - 2024-07-05
|
# 11.0.1 - 2024-07-05
|
||||||
|
|
||||||
- Fixed version number in the inline namespace
|
- Fixed version number in the inline namespace
|
||||||
@ -13,6 +708,9 @@
|
|||||||
(https://github.com/fmtlib/fmt/pull/4034,
|
(https://github.com/fmtlib/fmt/pull/4034,
|
||||||
https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus.
|
https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus.
|
||||||
|
|
||||||
|
- Fixed ADL issues in `fmt::printf` when using C++20
|
||||||
|
(https://github.com/fmtlib/fmt/pull/4042). Thanks @toge.
|
||||||
|
|
||||||
- Removed a redundant check in the formatter for `std::expected`
|
- Removed a redundant check in the formatter for `std::expected`
|
||||||
(https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus.
|
(https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus.
|
||||||
|
|
||||||
@ -238,6 +936,9 @@
|
|||||||
- Fixed handling of negative ids in `fmt::basic_format_args::get`
|
- Fixed handling of negative ids in `fmt::basic_format_args::get`
|
||||||
(https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota.
|
(https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota.
|
||||||
|
|
||||||
|
- Fixed handling of a buffer boundary on flush
|
||||||
|
(https://github.com/fmtlib/fmt/issues/4229).
|
||||||
|
|
||||||
- Improved named argument validation
|
- Improved named argument validation
|
||||||
(https://github.com/fmtlib/fmt/issues/3817).
|
(https://github.com/fmtlib/fmt/issues/3817).
|
||||||
|
|
||||||
|
|||||||
74
README.md
74
README.md
@ -4,14 +4,15 @@
|
|||||||
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
|
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
|
||||||
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
|
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
|
||||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
|
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
|
||||||
[](https://stackoverflow.com/questions/tagged/fmt)
|
[](https://www.bestpractices.dev/projects/8880)
|
||||||
[](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
|
[](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
|
||||||
|
[](https://stackoverflow.com/questions/tagged/fmt)
|
||||||
|
|
||||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||||
alternative to C stdio and C++ iostreams.
|
alternative to C stdio and C++ iostreams.
|
||||||
|
|
||||||
If you like this project, please consider donating to one of the funds
|
If you like this project, please consider donating to one of the funds
|
||||||
that help victims of the war in Ukraine: <https://www.stopputin.net/>.
|
that help victims of the war in Ukraine: <https://u24.gov.ua/>.
|
||||||
|
|
||||||
[Documentation](https://fmt.dev)
|
[Documentation](https://fmt.dev)
|
||||||
|
|
||||||
@ -24,12 +25,12 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
|
|||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Simple [format API](https://fmt.dev/latest/api.html) with positional
|
- Simple [format API](https://fmt.dev/latest/api/) with positional
|
||||||
arguments for localization
|
arguments for localization
|
||||||
- Implementation of [C++20
|
- Implementation of [C++20
|
||||||
std::format](https://en.cppreference.com/w/cpp/utility/format) and
|
std::format](https://en.cppreference.com/w/cpp/utility/format) and
|
||||||
[C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
|
[C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
|
||||||
- [Format string syntax](https://fmt.dev/latest/syntax.html) similar
|
- [Format string syntax](https://fmt.dev/latest/syntax/) similar
|
||||||
to Python\'s
|
to Python\'s
|
||||||
[format](https://docs.python.org/3/library/stdtypes.html#str.format)
|
[format](https://docs.python.org/3/library/stdtypes.html#str.format)
|
||||||
- Fast IEEE 754 floating-point formatter with correct rounding,
|
- Fast IEEE 754 floating-point formatter with correct rounding,
|
||||||
@ -37,17 +38,17 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
|
|||||||
[Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
|
[Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
|
||||||
- Portable Unicode support
|
- Portable Unicode support
|
||||||
- Safe [printf
|
- Safe [printf
|
||||||
implementation](https://fmt.dev/latest/api.html#printf-formatting)
|
implementation](https://fmt.dev/latest/api/#printf-formatting)
|
||||||
including the POSIX extension for positional arguments
|
including the POSIX extension for positional arguments
|
||||||
- Extensibility: [support for user-defined
|
- Extensibility: [support for user-defined
|
||||||
types](https://fmt.dev/latest/api.html#formatting-user-defined-types)
|
types](https://fmt.dev/latest/api/#formatting-user-defined-types)
|
||||||
- High performance: faster than common standard library
|
- High performance: faster than common standard library
|
||||||
implementations of `(s)printf`, iostreams, `to_string` and
|
implementations of `(s)printf`, iostreams, `to_string` and
|
||||||
`to_chars`, see [Speed tests](#speed-tests) and [Converting a
|
`to_chars`, see [Speed tests](#speed-tests) and [Converting a
|
||||||
hundred million integers to strings per
|
hundred million integers to strings per
|
||||||
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
|
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
|
||||||
- Small code size both in terms of source code with the minimum
|
- Small code size both in terms of source code with the minimum
|
||||||
configuration consisting of just three files, `core.h`, `format.h`
|
configuration consisting of just three files, `base.h`, `format.h`
|
||||||
and `format-inl.h`, and compiled code; see [Compile time and code
|
and `format-inl.h`, and compiled code; see [Compile time and code
|
||||||
bloat](#compile-time-and-code-bloat)
|
bloat](#compile-time-and-code-bloat)
|
||||||
- Reliability: the library has an extensive set of
|
- Reliability: the library has an extensive set of
|
||||||
@ -58,8 +59,8 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
|
|||||||
buffer overflow errors
|
buffer overflow errors
|
||||||
- Ease of use: small self-contained code base, no external
|
- Ease of use: small self-contained code base, no external
|
||||||
dependencies, permissive MIT
|
dependencies, permissive MIT
|
||||||
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
|
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE)
|
||||||
- [Portability](https://fmt.dev/latest/index.html#portability) with
|
- [Portability](https://fmt.dev/latest/#portability) with
|
||||||
consistent output across platforms and support for older compilers
|
consistent output across platforms and support for older compilers
|
||||||
- Clean warning-free codebase even on high warning levels such as
|
- Clean warning-free codebase even on high warning levels such as
|
||||||
`-Wall -Wextra -pedantic`
|
`-Wall -Wextra -pedantic`
|
||||||
@ -74,7 +75,7 @@ See the [documentation](https://fmt.dev) for more details.
|
|||||||
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
|
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
|
||||||
|
|
||||||
``` c++
|
``` c++
|
||||||
#include <fmt/core.h>
|
#include <fmt/base.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
fmt::print("Hello, world!\n");
|
fmt::print("Hello, world!\n");
|
||||||
@ -149,8 +150,8 @@ int main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This can be [5 to 9 times faster than
|
This can be [up to 9 times faster than `fprintf`](
|
||||||
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
|
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
|
||||||
|
|
||||||
**Print with colors and text styles**
|
**Print with colors and text styles**
|
||||||
|
|
||||||
@ -177,17 +178,17 @@ Output on a modern terminal with Unicode support:
|
|||||||
|
|
||||||
| Library | Method | Run Time, s |
|
| Library | Method | Run Time, s |
|
||||||
|-------------------|---------------|-------------|
|
|-------------------|---------------|-------------|
|
||||||
| libc | printf | 0.91 |
|
| libc | printf | 0.66 |
|
||||||
| libc++ | std::ostream | 2.49 |
|
| libc++ | std::ostream | 1.63 |
|
||||||
| {fmt} 9.1 | fmt::print | 0.74 |
|
| {fmt} 12.1 | fmt::print | 0.44 |
|
||||||
| Boost Format 1.80 | boost::format | 6.26 |
|
| Boost Format 1.88 | boost::format | 3.89 |
|
||||||
| Folly Format | folly::format | 1.87 |
|
| Folly Format | folly::format | 1.28 |
|
||||||
|
|
||||||
{fmt} is the fastest of the benchmarked methods, \~20% faster than
|
{fmt} is the fastest of the benchmarked methods, \~50% faster than
|
||||||
`printf`.
|
`printf`.
|
||||||
|
|
||||||
The above results were generated by building `tinyformat_test.cpp` on
|
The above results were generated by building `tinyformat_test.cpp` on
|
||||||
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
|
macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
|
||||||
taking the best of three runs. In the test, the format string
|
taking the best of three runs. In the test, the format string
|
||||||
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
|
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
|
||||||
times with output sent to `/dev/null`; for further details refer to the
|
times with output sent to `/dev/null`; for further details refer to the
|
||||||
@ -215,26 +216,26 @@ in the following tables.
|
|||||||
|
|
||||||
**Optimized build (-O3)**
|
**Optimized build (-O3)**
|
||||||
|
|
||||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||||
|---------------|-----------------|----------------------|--------------------|
|
|-----------------|-----------------|----------------------|--------------------|
|
||||||
| printf | 1.6 | 54 | 50 |
|
| printf | 1.6 | 54 | 50 |
|
||||||
| IOStreams | 25.9 | 98 | 84 |
|
| IOStreams | 28.4 | 98 | 84 |
|
||||||
| fmt 83652df | 4.8 | 54 | 50 |
|
| {fmt} `1122268` | 5.0 | 54 | 50 |
|
||||||
| tinyformat | 29.1 | 161 | 136 |
|
| tinyformat | 32.6 | 164 | 136 |
|
||||||
| Boost Format | 55.0 | 530 | 317 |
|
| Boost Format | 55.0 | 530 | 317 |
|
||||||
|
|
||||||
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
|
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
|
||||||
binary size (within a rounding error on this system).
|
binary size (within a rounding error on this system).
|
||||||
|
|
||||||
**Non-optimized build**
|
**Non-optimized build**
|
||||||
|
|
||||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||||
|---------------|-----------------|----------------------|--------------------|
|
|-----------------|-----------------|----------------------|--------------------|
|
||||||
| printf | 1.4 | 54 | 50 |
|
| printf | 1.4 | 54 | 50 |
|
||||||
| IOStreams | 23.4 | 92 | 68 |
|
| IOStreams | 27.0 | 88 | 68 |
|
||||||
| {fmt} 83652df | 4.4 | 89 | 85 |
|
| {fmt} `1122268` | 4.7 | 87 | 84 |
|
||||||
| tinyformat | 24.5 | 204 | 161 |
|
| tinyformat | 28.1 | 185 | 145 |
|
||||||
| Boost Format | 36.4 | 831 | 462 |
|
| Boost Format | 38.9 | 678 | 381 |
|
||||||
|
|
||||||
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
|
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
|
||||||
to compare formatting function overhead only. Boost Format is a
|
to compare formatting function overhead only. Boost Format is a
|
||||||
@ -243,7 +244,7 @@ header-only library so it doesn\'t provide any linkage options.
|
|||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
Please refer to [Building the
|
Please refer to [Building the
|
||||||
library](https://fmt.dev/latest/usage.html#building-the-library) for
|
library](https://fmt.dev/latest/get-started/#building-from-source) for
|
||||||
instructions on how to build the library and run the unit tests.
|
instructions on how to build the library and run the unit tests.
|
||||||
|
|
||||||
Benchmarks reside in a separate repository,
|
Benchmarks reside in a separate repository,
|
||||||
@ -291,13 +292,14 @@ converts to `std::print`.)
|
|||||||
- [ccache](https://ccache.dev/): a compiler cache
|
- [ccache](https://ccache.dev/): a compiler cache
|
||||||
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
|
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
|
||||||
analytical database management system
|
analytical database management system
|
||||||
|
- [ContextVision](https://www.contextvision.com/): medical imaging software
|
||||||
- [Contour](https://github.com/contour-terminal/contour/): a modern
|
- [Contour](https://github.com/contour-terminal/contour/): a modern
|
||||||
terminal emulator
|
terminal emulator
|
||||||
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
|
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
|
||||||
underwater vehicle
|
underwater vehicle
|
||||||
- [Drake](https://drake.mit.edu/): a planning, control, and analysis
|
- [Drake](https://drake.mit.edu/): a planning, control, and analysis
|
||||||
toolbox for nonlinear dynamical systems (MIT)
|
toolbox for nonlinear dynamical systems (MIT)
|
||||||
- [Envoy](https://lyft.github.io/envoy/): C++ L7 proxy and
|
- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and
|
||||||
communication bus (Lyft)
|
communication bus (Lyft)
|
||||||
- [FiveM](https://fivem.net/): a modification framework for GTA V
|
- [FiveM](https://fivem.net/): a modification framework for GTA V
|
||||||
- [fmtlog](https://github.com/MengRao/fmtlog): a performant
|
- [fmtlog](https://github.com/MengRao/fmtlog): a performant
|
||||||
@ -456,7 +458,7 @@ second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).
|
|||||||
|
|
||||||
# Documentation License
|
# Documentation License
|
||||||
|
|
||||||
The [Format String Syntax](https://fmt.dev/latest/syntax.html) section
|
The [Format String Syntax](https://fmt.dev/latest/syntax/) section
|
||||||
in the documentation is based on the one from Python [string module
|
in the documentation is based on the one from Python [string module
|
||||||
documentation](https://docs.python.org/3/library/string.html#module-string).
|
documentation](https://docs.python.org/3/library/string.html#module-string).
|
||||||
For this reason, the documentation is distributed under the Python
|
For this reason, the documentation is distributed under the Python
|
||||||
|
|||||||
@ -674,7 +674,7 @@
|
|||||||
https://github.com/fmtlib/fmt/issues/1747,
|
https://github.com/fmtlib/fmt/issues/1747,
|
||||||
https://github.com/fmtlib/fmt/pull/1750).
|
https://github.com/fmtlib/fmt/pull/1750).
|
||||||
Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10,
|
Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10,
|
||||||
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink,
|
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime,
|
||||||
@tohammer and @0x8000-0000.
|
@tohammer and @0x8000-0000.
|
||||||
|
|
||||||
# 6.2.1 - 2020-05-09
|
# 6.2.1 - 2020-05-09
|
||||||
|
|||||||
202
doc/api.md
202
doc/api.md
@ -14,7 +14,7 @@ The {fmt} library API consists of the following components:
|
|||||||
- [`fmt/os.h`](#os-api): system APIs
|
- [`fmt/os.h`](#os-api): system APIs
|
||||||
- [`fmt/ostream.h`](#ostream-api): `std::ostream` support
|
- [`fmt/ostream.h`](#ostream-api): `std::ostream` support
|
||||||
- [`fmt/args.h`](#args-api): dynamic argument lists
|
- [`fmt/args.h`](#args-api): dynamic argument lists
|
||||||
- [`fmt/printf.h`](#printf-api): `printf` formatting
|
- [`fmt/printf.h`](#printf-api): safe `printf`
|
||||||
- [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support
|
- [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support
|
||||||
|
|
||||||
All functions and types provided by the library reside in namespace `fmt`
|
All functions and types provided by the library reside in namespace `fmt`
|
||||||
@ -79,6 +79,8 @@ time formatting and [`fmt/std.h`](#std-api) for other standard library types.
|
|||||||
|
|
||||||
There are two ways to make a user-defined type formattable: providing a
|
There are two ways to make a user-defined type formattable: providing a
|
||||||
`format_as` function or specializing the `formatter` struct template.
|
`format_as` function or specializing the `formatter` struct template.
|
||||||
|
Formatting of non-void pointer types is intentionally disallowed and they
|
||||||
|
cannot be made formattable via either extension API.
|
||||||
|
|
||||||
Use `format_as` if you want to make your type formattable as some other
|
Use `format_as` if you want to make your type formattable as some other
|
||||||
type with the same format specifiers. The `format_as` function should
|
type with the same format specifiers. The `format_as` function should
|
||||||
@ -220,7 +222,7 @@ You can also write a formatter for a hierarchy of classes:
|
|||||||
```c++
|
```c++
|
||||||
// demo.h:
|
// demo.h:
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <fmt/core.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
struct A {
|
struct A {
|
||||||
virtual ~A() {}
|
virtual ~A() {}
|
||||||
@ -269,18 +271,16 @@ that support C++20 `consteval`. On older compilers you can use the
|
|||||||
|
|
||||||
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
|
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
|
||||||
|
|
||||||
::: basic_format_string
|
See [Type Erasure](#type-erasure) for an example of how to enable compile-time
|
||||||
|
checks in your own functions with `fmt::format_string` while avoiding template
|
||||||
|
bloat.
|
||||||
|
|
||||||
|
::: fstring
|
||||||
|
|
||||||
::: format_string
|
::: format_string
|
||||||
|
|
||||||
::: runtime(string_view)
|
::: runtime(string_view)
|
||||||
|
|
||||||
### Named Arguments
|
|
||||||
|
|
||||||
::: arg(const Char*, const T&)
|
|
||||||
|
|
||||||
Named arguments are not supported in compile-time checks at the moment.
|
|
||||||
|
|
||||||
### Type Erasure
|
### Type Erasure
|
||||||
|
|
||||||
You can create your own formatting function with compile-time checks and
|
You can create your own formatting function with compile-time checks and
|
||||||
@ -317,6 +317,12 @@ parameterized version.
|
|||||||
|
|
||||||
::: basic_format_arg
|
::: basic_format_arg
|
||||||
|
|
||||||
|
### Named Arguments
|
||||||
|
|
||||||
|
::: arg(const Char*, const T&)
|
||||||
|
|
||||||
|
Named arguments are not supported in compile-time checks at the moment.
|
||||||
|
|
||||||
### Compatibility
|
### Compatibility
|
||||||
|
|
||||||
::: basic_string_view
|
::: basic_string_view
|
||||||
@ -375,18 +381,17 @@ allocator:
|
|||||||
using custom_string =
|
using custom_string =
|
||||||
std::basic_string<char, std::char_traits<char>, custom_allocator>;
|
std::basic_string<char, std::char_traits<char>, custom_allocator>;
|
||||||
|
|
||||||
custom_string vformat(custom_allocator alloc, fmt::string_view format_str,
|
auto vformat(custom_allocator alloc, fmt::string_view fmt,
|
||||||
fmt::format_args args) {
|
fmt::format_args args) -> custom_string {
|
||||||
auto buf = custom_memory_buffer(alloc);
|
auto buf = custom_memory_buffer(alloc);
|
||||||
fmt::vformat_to(std::back_inserter(buf), format_str, args);
|
fmt::vformat_to(std::back_inserter(buf), fmt, args);
|
||||||
return custom_string(buf.data(), buf.size(), alloc);
|
return custom_string(buf.data(), buf.size(), alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ...Args>
|
template <typename ...Args>
|
||||||
inline custom_string format(custom_allocator alloc,
|
auto format(custom_allocator alloc, fmt::string_view fmt,
|
||||||
fmt::string_view format_str,
|
const Args& ... args) -> custom_string {
|
||||||
const Args& ... args) {
|
return vformat(alloc, fmt, fmt::make_format_args(args...));
|
||||||
return vformat(alloc, format_str, fmt::make_format_args(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
The allocator will be used for the output container only. Formatting
|
The allocator will be used for the output container only. Formatting
|
||||||
@ -400,7 +405,7 @@ All formatting is locale-independent by default. Use the `'L'` format
|
|||||||
specifier to insert the appropriate number separator characters from the
|
specifier to insert the appropriate number separator characters from the
|
||||||
locale:
|
locale:
|
||||||
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/format.h>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
|
||||||
std::locale::global(std::locale("en_US.UTF-8"));
|
std::locale::global(std::locale("en_US.UTF-8"));
|
||||||
@ -410,11 +415,11 @@ locale:
|
|||||||
that take `std::locale` as a parameter. The locale type is a template
|
that take `std::locale` as a parameter. The locale type is a template
|
||||||
parameter to avoid the expensive `<locale>` include.
|
parameter to avoid the expensive `<locale>` include.
|
||||||
|
|
||||||
::: format(const Locale&, format_string<T...>, T&&...)
|
::: format(locale_ref, format_string<T...>, T&&...)
|
||||||
|
|
||||||
::: format_to(OutputIt, const Locale&, format_string<T...>, T&&...)
|
::: format_to(OutputIt, locale_ref, format_string<T...>, T&&...)
|
||||||
|
|
||||||
::: formatted_size(const Locale&, format_string<T...>, T&&...)
|
::: formatted_size(locale_ref, format_string<T...>, T&&...)
|
||||||
|
|
||||||
<a id="legacy-checks"></a>
|
<a id="legacy-checks"></a>
|
||||||
### Legacy Compile-Time Checks
|
### Legacy Compile-Time Checks
|
||||||
@ -470,9 +475,9 @@ chrono-format-specifications).
|
|||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
std::time_t t = std::time(nullptr);
|
auto now = std::chrono::system_clock::now();
|
||||||
|
|
||||||
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
fmt::print("The date is {:%Y-%m-%d}.\n", now);
|
||||||
// Output: The date is 2020-11-07.
|
// Output: The date is 2020-11-07.
|
||||||
// (with 2020-11-07 replaced by the current date)
|
// (with 2020-11-07 replaced by the current date)
|
||||||
|
|
||||||
@ -485,8 +490,6 @@ chrono-format-specifications).
|
|||||||
// Output: strftime-like format: 03:15:30
|
// Output: strftime-like format: 03:15:30
|
||||||
}
|
}
|
||||||
|
|
||||||
::: localtime(std::time_t)
|
|
||||||
|
|
||||||
::: gmtime(std::time_t)
|
::: gmtime(std::time_t)
|
||||||
|
|
||||||
<a id="std-api"></a>
|
<a id="std-api"></a>
|
||||||
@ -498,10 +501,13 @@ chrono-format-specifications).
|
|||||||
- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
|
- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
|
||||||
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
|
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
|
||||||
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
|
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
|
||||||
|
- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception)
|
||||||
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
|
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
|
||||||
- [`std::monostate`](https://en.cppreference.com/w/cpp/utility/variant/monostate)
|
- [`std::monostate`](
|
||||||
|
https://en.cppreference.com/w/cpp/utility/variant/monostate)
|
||||||
- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional)
|
- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional)
|
||||||
- [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location)
|
- [`std::source_location`](
|
||||||
|
https://en.cppreference.com/w/cpp/utility/source_location)
|
||||||
- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id)
|
- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id)
|
||||||
- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant)
|
- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant)
|
||||||
|
|
||||||
@ -509,7 +515,7 @@ chrono-format-specifications).
|
|||||||
|
|
||||||
::: ptr(const std::shared_ptr<T>&)
|
::: ptr(const std::shared_ptr<T>&)
|
||||||
|
|
||||||
### Formatting Variants
|
### Variants
|
||||||
|
|
||||||
A `std::variant` is only formattable if every variant alternative is
|
A `std::variant` is only formattable if every variant alternative is
|
||||||
formattable, and requires the `__cpp_lib_variant` [library
|
formattable, and requires the `__cpp_lib_variant` [library
|
||||||
@ -525,39 +531,87 @@ feature](https://en.cppreference.com/w/cpp/feature_test).
|
|||||||
fmt::print("{}", std::variant<std::monostate, char>());
|
fmt::print("{}", std::variant<std::monostate, char>());
|
||||||
// Output: variant(monostate)
|
// Output: variant(monostate)
|
||||||
|
|
||||||
<a id="compile-api"></a>
|
## Bit-Fields and Packed Structs
|
||||||
## Format String Compilation
|
|
||||||
|
|
||||||
`fmt/compile.h` provides format string compilation enabled via the
|
To format a bit-field or a field of a struct with `__attribute__((packed))`
|
||||||
`FMT_COMPILE` macro or the `_cf` user-defined literal defined in
|
applied to it, you need to convert it to the underlying or compatible type via
|
||||||
namespace `fmt::literals`. Format strings marked with `FMT_COMPILE`
|
a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)):
|
||||||
or `_cf` are parsed, checked and converted into efficient formatting
|
|
||||||
code at compile-time. This supports arguments of built-in and string
|
```c++
|
||||||
types as well as user-defined types with `format` functions taking
|
struct smol {
|
||||||
|
int bit : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto s = smol();
|
||||||
|
fmt::print("{}", +s.bit);
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a known limitation of "perfect" forwarding in C++.
|
||||||
|
|
||||||
|
<a id="compile-api"></a>
|
||||||
|
## Compile-Time Support
|
||||||
|
|
||||||
|
`fmt/compile.h` provides format string compilation and compile-time
|
||||||
|
(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf`
|
||||||
|
user-defined literal defined in namespace `fmt::literals`. Format strings
|
||||||
|
marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into
|
||||||
|
efficient formatting code at compile-time. This supports arguments of built-in
|
||||||
|
and string types as well as user-defined types with `format` methods taking
|
||||||
the format context type as a template parameter in their `formatter`
|
the format context type as a template parameter in their `formatter`
|
||||||
specializations. For example:
|
specializations. For example ([run](https://www.godbolt.org/z/3c13erEoq)):
|
||||||
|
|
||||||
|
struct point {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
template <> struct fmt::formatter<point> {
|
template <> struct fmt::formatter<point> {
|
||||||
constexpr auto parse(format_parse_context& ctx);
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const point& p, FormatContext& ctx) const;
|
auto format(const point& p, FormatContext& ctx) const {
|
||||||
|
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using namespace fmt::literals;
|
||||||
|
std::string s = fmt::format("{}"_cf, point(4, 2));
|
||||||
|
|
||||||
Format string compilation can generate more binary code compared to the
|
Format string compilation can generate more binary code compared to the
|
||||||
default API and is only recommended in places where formatting is a
|
default API and is only recommended in places where formatting is a
|
||||||
performance bottleneck.
|
performance bottleneck.
|
||||||
|
|
||||||
::: FMT_COMPILE
|
The same APIs support formatting at compile time e.g. in `constexpr`
|
||||||
|
and `consteval` functions. Additionally there is an experimental
|
||||||
|
`FMT_STATIC_FORMAT` that allows formatting into a string of the exact
|
||||||
|
required size at compile time. Compile-time formatting works with built-in
|
||||||
|
and user-defined formatters that have `constexpr` `format` methods.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
template <> struct fmt::formatter<point> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
constexpr auto format(const point& p, FormatContext& ctx) const {
|
||||||
|
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto s = FMT_STATIC_FORMAT("{}", point(4, 2));
|
||||||
|
const char* cstr = s.c_str(); // Points the static string "(4, 2)".
|
||||||
|
|
||||||
::: operator""_cf
|
::: operator""_cf
|
||||||
|
|
||||||
|
::: FMT_COMPILE
|
||||||
|
|
||||||
|
::: FMT_STATIC_FORMAT
|
||||||
|
|
||||||
<a id="color-api"></a>
|
<a id="color-api"></a>
|
||||||
## Terminal Colors and Text Styles
|
## Terminal Colors and Text Styles
|
||||||
|
|
||||||
`fmt/color.h` provides support for terminal color and text style output.
|
`fmt/color.h` provides support for terminal color and text style output.
|
||||||
|
|
||||||
::: print(const text_style&, format_string<T...>, T&&...)
|
::: print(text_style, format_string<T...>, T&&...)
|
||||||
|
|
||||||
::: fg(detail::color_type)
|
::: fg(detail::color_type)
|
||||||
|
|
||||||
@ -570,6 +624,8 @@ performance bottleneck.
|
|||||||
|
|
||||||
::: ostream
|
::: ostream
|
||||||
|
|
||||||
|
::: output_file(cstring_view, T...)
|
||||||
|
|
||||||
::: windows_error
|
::: windows_error
|
||||||
|
|
||||||
<a id="ostream-api"></a>
|
<a id="ostream-api"></a>
|
||||||
@ -609,7 +665,7 @@ that can be used to construct format argument lists dynamically.
|
|||||||
::: dynamic_format_arg_store
|
::: dynamic_format_arg_store
|
||||||
|
|
||||||
<a id="printf-api"></a>
|
<a id="printf-api"></a>
|
||||||
## `printf` Formatting
|
## Safe `printf`
|
||||||
|
|
||||||
The header `fmt/printf.h` provides `printf`-like formatting
|
The header `fmt/printf.h` provides `printf`-like formatting
|
||||||
functionality. The following functions use [printf format string
|
functionality. The following functions use [printf format string
|
||||||
@ -620,9 +676,9 @@ if an argument type doesn't match its format specification.
|
|||||||
|
|
||||||
::: printf(string_view, const T&...)
|
::: printf(string_view, const T&...)
|
||||||
|
|
||||||
::: fprintf(std::FILE*, const S&, const T&...)
|
::: fprintf(std::FILE*, string_view, const T&...)
|
||||||
|
|
||||||
::: sprintf(const S&, const T&...)
|
::: sprintf(string_view, const T&...)
|
||||||
|
|
||||||
<a id="xchar-api"></a>
|
<a id="xchar-api"></a>
|
||||||
## Wide Strings
|
## Wide Strings
|
||||||
@ -630,8 +686,6 @@ if an argument type doesn't match its format specification.
|
|||||||
The optional header `fmt/xchar.h` provides support for `wchar_t` and
|
The optional header `fmt/xchar.h` provides support for `wchar_t` and
|
||||||
exotic character types.
|
exotic character types.
|
||||||
|
|
||||||
::: is_char
|
|
||||||
|
|
||||||
::: wstring_view
|
::: wstring_view
|
||||||
|
|
||||||
::: wformat_context
|
::: wformat_context
|
||||||
@ -646,5 +700,63 @@ following differences:
|
|||||||
|
|
||||||
- Names are defined in the `fmt` namespace instead of `std` to avoid
|
- Names are defined in the `fmt` namespace instead of `std` to avoid
|
||||||
collisions with standard library implementations.
|
collisions with standard library implementations.
|
||||||
|
|
||||||
- Width calculation doesn't use grapheme clusterization. The latter has
|
- Width calculation doesn't use grapheme clusterization. The latter has
|
||||||
been implemented in a separate branch but hasn't been integrated yet.
|
been implemented in a separate branch but hasn't been integrated yet.
|
||||||
|
|
||||||
|
- The default floating-point representation in {fmt} uses the smallest
|
||||||
|
precision that provides round-trip guarantees similarly to other languages
|
||||||
|
like Java and Python. `std::format` is currently specified in terms of
|
||||||
|
`std::to_chars` which tries to generate the smallest number of characters
|
||||||
|
(ignoring redundant digits and sign in exponent) and may produce more
|
||||||
|
decimal digits than necessary.
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
{fmt} provides configuration via CMake options and preprocessor macros to
|
||||||
|
enable or disable features and to optimize for binary size. For example, you
|
||||||
|
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
|
||||||
|
configuring CMake.
|
||||||
|
|
||||||
|
### CMake Options
|
||||||
|
|
||||||
|
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
|
||||||
|
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
|
||||||
|
Windows/MSVC. Unicode support is always enabled on other platforms.
|
||||||
|
|
||||||
|
### Macros
|
||||||
|
|
||||||
|
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
|
||||||
|
alternative to using the `fmt::fmt-header-only` CMake target.
|
||||||
|
Default: not defined.
|
||||||
|
|
||||||
|
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
|
||||||
|
Default: `1` (`0` if compiled with `-fno-exceptions`).
|
||||||
|
|
||||||
|
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
|
||||||
|
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
|
||||||
|
|
||||||
|
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
|
||||||
|
custom `fmt::assert_fail` function which is called on assertion failures and,
|
||||||
|
if exceptions are disabled, on runtime errors. Default: `0`.
|
||||||
|
|
||||||
|
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
|
||||||
|
arithmetic and string types other than `int`. This reduces library size at
|
||||||
|
the cost of per-call overhead. Default: `1`.
|
||||||
|
|
||||||
|
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
|
||||||
|
- `0` - off (default)
|
||||||
|
- `1` - disables locale support and applies some optimizations
|
||||||
|
- `2` - disables some Unicode features, named arguments and applies more
|
||||||
|
aggressive optimizations
|
||||||
|
|
||||||
|
### Binary Size Optimization
|
||||||
|
|
||||||
|
To minimize the binary footprint of {fmt} as much as possible at the cost of
|
||||||
|
some features, you can use the following configuration:
|
||||||
|
|
||||||
|
- CMake options:
|
||||||
|
- `FMT_OS=OFF`
|
||||||
|
- Macros:
|
||||||
|
- `FMT_BUILTIN_TYPES=0`
|
||||||
|
- `FMT_OPTIMIZE_SIZE=2`
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
pre > code.decl {
|
pre > code.decl {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,6 +78,17 @@ community contributors. If the version is out of date, please [create an
|
|||||||
issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg
|
issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg
|
||||||
repository. -->
|
repository. -->
|
||||||
|
|
||||||
|
### Conan
|
||||||
|
|
||||||
|
You can download and install {fmt} using the [Conan](https://conan.io/) package manager:
|
||||||
|
|
||||||
|
conan install -r conancenter --requires="fmt/[*]" --build=missing
|
||||||
|
|
||||||
|
<!-- The {fmt} package in Conan Center is maintained by
|
||||||
|
[ConanCenterIndex](https://github.com/conan-io/conan-center-index) community.
|
||||||
|
If the version is out of date or the package does not work,
|
||||||
|
please create an issue or pull request on the Conan Center Index repository. -->
|
||||||
|
|
||||||
## Building from Source
|
## Building from Source
|
||||||
|
|
||||||
CMake works by generating native makefiles or project files that can be
|
CMake works by generating native makefiles or project files that can be
|
||||||
@ -202,7 +213,7 @@ For a static build, use the following subproject definition:
|
|||||||
|
|
||||||
For the header-only version, use:
|
For the header-only version, use:
|
||||||
|
|
||||||
fmt = subproject('fmt')
|
fmt = subproject('fmt', default_options: ['header-only=true'])
|
||||||
fmt_dep = fmt.get_variable('fmt_header_only_dep')
|
fmt_dep = fmt.get_variable('fmt_header_only_dep')
|
||||||
|
|
||||||
### Android NDK
|
### Android NDK
|
||||||
|
|||||||
@ -76,7 +76,7 @@ hide:
|
|||||||
<p>
|
<p>
|
||||||
The default is <b>locale-independent</b>, but you can opt into localized
|
The default is <b>locale-independent</b>, but you can opt into localized
|
||||||
formatting and {fmt} makes it work with Unicode, addressing issues in the
|
formatting and {fmt} makes it work with Unicode, addressing issues in the
|
||||||
standard libary.
|
standard library.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -122,8 +122,8 @@ hide:
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The library is highly portable and requires only a minimal <b>subset of
|
The library is highly portable and requires only a minimal <b>subset of
|
||||||
C++11</b> features which are available in GCC 4.8, Clang 3.4, MSVC 19.0
|
C++11</b> features which are available in GCC 4.9, Clang 3.6, MSVC 19.10
|
||||||
(2015) and later. Newer compiler and standard library features are used
|
(2017) and later. Newer compiler and standard library features are used
|
||||||
if available, and enable additional functionality.
|
if available, and enable additional functionality.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -251,7 +251,7 @@ The available integer presentation types are:
|
|||||||
<td><code>'b'</code></td>
|
<td><code>'b'</code></td>
|
||||||
<td>
|
<td>
|
||||||
Binary format. Outputs the number in base 2. Using the <code>'#'</code>
|
Binary format. Outputs the number in base 2. Using the <code>'#'</code>
|
||||||
option with this type adds the prefix <code>"0b"</code> to the output value.
|
option with this type adds the prefix <code>"0b"</code> to the output value.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -589,8 +589,7 @@ The available presentation types (*chrono_type*) are:
|
|||||||
represented with seconds, then the format is a decimal floating-point number
|
represented with seconds, then the format is a decimal floating-point number
|
||||||
with a fixed format and a precision matching that of the precision of the
|
with a fixed format and a precision matching that of the precision of the
|
||||||
input (or to a microseconds precision if the conversion to floating-point
|
input (or to a microseconds precision if the conversion to floating-point
|
||||||
decimal seconds cannot be made within 18 fractional digits). The character
|
decimal seconds cannot be made within 18 fractional digits). The modified
|
||||||
for the decimal point is localized according to the locale. The modified
|
|
||||||
command <code>%OS</code> produces the locale's alternative representation.
|
command <code>%OS</code> produces the locale's alternative representation.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -707,19 +706,19 @@ The available padding modifiers (*padding_modifier*) are:
|
|||||||
|
|
||||||
| Type | Meaning |
|
| Type | Meaning |
|
||||||
|-------|-----------------------------------------|
|
|-------|-----------------------------------------|
|
||||||
| `'-'` | Pad a numeric result with spaces. |
|
| `'_'` | Pad a numeric result with spaces. |
|
||||||
| `'_'` | Do not pad a numeric result string. |
|
| `'-'` | Do not pad a numeric result string. |
|
||||||
| `'0'` | Pad a numeric result string with zeros. |
|
| `'0'` | Pad a numeric result string with zeros. |
|
||||||
|
|
||||||
These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
|
These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
|
||||||
`'V'` and `'W'` presentation types.
|
`'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types.
|
||||||
|
|
||||||
## Range Format Specifications
|
## Range Format Specifications
|
||||||
|
|
||||||
Format specifications for range types have the following syntax:
|
Format specifications for range types have the following syntax:
|
||||||
|
|
||||||
<pre><code class="language-json"
|
<pre><code class="language-json"
|
||||||
>range_format_spec ::= ["n"][range_type][range_underlying_spec]</code>
|
>range_format_spec ::= ["n"][range_type][":" range_underlying_spec]</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
The `'n'` option formats the range without the opening and closing brackets.
|
The `'n'` option formats the range without the opening and closing brackets.
|
||||||
@ -762,14 +761,16 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
|
|||||||
// Output: [h, e, l, l, o]
|
// Output: [h, e, l, l, o]
|
||||||
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
|
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
|
||||||
// Output: [104, 101, 108, 108, 111]
|
// Output: [104, 101, 108, 108, 111]
|
||||||
|
fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e});
|
||||||
|
// Output: 3.141593, 2.718282
|
||||||
```
|
```
|
||||||
|
|
||||||
## Format Examples
|
## Format Examples
|
||||||
|
|
||||||
This section contains examples of the format syntax and comparison with
|
This section contains examples of the format syntax and comparison with
|
||||||
the printf formatting.
|
the `printf` formatting.
|
||||||
|
|
||||||
In most of the cases the syntax is similar to the printf formatting,
|
In most of the cases the syntax is similar to the `printf` formatting,
|
||||||
with the addition of the `{}` and with `:` used instead of `%`. For
|
with the addition of the `{}` and with `:` used instead of `%`. For
|
||||||
example, `"%03.2f"` can be translated to `"{:03.2f}"`.
|
example, `"%03.2f"` can be translated to `"{:03.2f}"`.
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
#include "format.h" // std_string_view
|
#include "format.h" // std_string_view
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename T> struct is_reference_wrapper : std::false_type {};
|
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||||
@ -72,19 +71,13 @@ class dynamic_arg_list {
|
|||||||
* It can be implicitly converted into `fmt::basic_format_args` for passing
|
* It can be implicitly converted into `fmt::basic_format_args` for passing
|
||||||
* into type-erased formatting functions such as `fmt::vformat`.
|
* into type-erased formatting functions such as `fmt::vformat`.
|
||||||
*/
|
*/
|
||||||
template <typename Context>
|
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
|
||||||
class dynamic_format_arg_store
|
|
||||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
|
||||||
// Workaround a GCC template argument substitution bug.
|
|
||||||
: public basic_format_args<Context>
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
private:
|
private:
|
||||||
using char_type = typename Context::char_type;
|
using char_type = typename Context::char_type;
|
||||||
|
|
||||||
template <typename T> struct need_copy {
|
template <typename T> struct need_copy {
|
||||||
static constexpr detail::type mapped_type =
|
static constexpr detail::type mapped_type =
|
||||||
detail::mapped_type_constant<T, Context>::value;
|
detail::mapped_type_constant<T, char_type>::value;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
value = !(detail::is_reference_wrapper<T>::value ||
|
value = !(detail::is_reference_wrapper<T>::value ||
|
||||||
@ -97,7 +90,7 @@ class dynamic_format_arg_store
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using stored_type = conditional_t<
|
using stored_t = conditional_t<
|
||||||
std::is_convertible<T, std::basic_string<char_type>>::value &&
|
std::is_convertible<T, std::basic_string<char_type>>::value &&
|
||||||
!detail::is_reference_wrapper<T>::value,
|
!detail::is_reference_wrapper<T>::value,
|
||||||
std::basic_string<char_type>, T>;
|
std::basic_string<char_type>, T>;
|
||||||
@ -112,41 +105,37 @@ class dynamic_format_arg_store
|
|||||||
|
|
||||||
friend class basic_format_args<Context>;
|
friend class basic_format_args<Context>;
|
||||||
|
|
||||||
auto get_types() const -> unsigned long long {
|
|
||||||
return detail::is_unpacked_bit | data_.size() |
|
|
||||||
(named_info_.empty()
|
|
||||||
? 0ULL
|
|
||||||
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data() const -> const basic_format_arg<Context>* {
|
auto data() const -> const basic_format_arg<Context>* {
|
||||||
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void emplace_arg(const T& arg) {
|
template <typename T> void emplace_arg(const T& arg) {
|
||||||
data_.emplace_back(detail::make_arg<Context>(arg));
|
data_.emplace_back(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||||
if (named_info_.empty()) {
|
if (named_info_.empty())
|
||||||
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
|
||||||
data_.insert(data_.begin(), {zero_ptr, 0});
|
data_.emplace_back(detail::unwrap(arg.value));
|
||||||
}
|
|
||||||
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
|
||||||
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||||
data->pop_back();
|
data->pop_back();
|
||||||
};
|
};
|
||||||
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||||
guard{&data_, pop_one};
|
guard{&data_, pop_one};
|
||||||
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||||
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
data_[0] = {named_info_.data(), named_info_.size()};
|
||||||
guard.release();
|
guard.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
constexpr dynamic_format_arg_store() = default;
|
constexpr dynamic_format_arg_store() = default;
|
||||||
|
|
||||||
|
operator basic_format_args<Context>() const {
|
||||||
|
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
|
||||||
|
!named_info_.empty());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an argument into the dynamic store for later passing to a formatting
|
* Adds an argument into the dynamic store for later passing to a formatting
|
||||||
* function.
|
* function.
|
||||||
@ -164,7 +153,7 @@ class dynamic_format_arg_store
|
|||||||
*/
|
*/
|
||||||
template <typename T> void push_back(const T& arg) {
|
template <typename T> void push_back(const T& arg) {
|
||||||
if (detail::const_check(need_copy<T>::value))
|
if (detail::const_check(need_copy<T>::value))
|
||||||
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
|
||||||
else
|
else
|
||||||
emplace_arg(detail::unwrap(arg));
|
emplace_arg(detail::unwrap(arg));
|
||||||
}
|
}
|
||||||
@ -200,7 +189,7 @@ class dynamic_format_arg_store
|
|||||||
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||||
if (detail::const_check(need_copy<T>::value)) {
|
if (detail::const_check(need_copy<T>::value)) {
|
||||||
emplace_arg(
|
emplace_arg(
|
||||||
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
|
||||||
} else {
|
} else {
|
||||||
emplace_arg(fmt::arg(arg_name, arg.value));
|
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||||
}
|
}
|
||||||
@ -210,17 +199,20 @@ class dynamic_format_arg_store
|
|||||||
void clear() {
|
void clear() {
|
||||||
data_.clear();
|
data_.clear();
|
||||||
named_info_.clear();
|
named_info_.clear();
|
||||||
dynamic_args_ = detail::dynamic_arg_list();
|
dynamic_args_ = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reserves space to store at least `new_cap` arguments including
|
/// Reserves space to store at least `new_cap` arguments including
|
||||||
/// `new_cap_named` named arguments.
|
/// `new_cap_named` named arguments.
|
||||||
void reserve(size_t new_cap, size_t new_cap_named) {
|
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||||
FMT_ASSERT(new_cap >= new_cap_named,
|
FMT_ASSERT(new_cap >= new_cap_named,
|
||||||
"Set of arguments includes set of named arguments");
|
"set of arguments includes set of named arguments");
|
||||||
data_.reserve(new_cap);
|
data_.reserve(new_cap);
|
||||||
named_info_.reserve(new_cap_named);
|
named_info_.reserve(new_cap_named);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of elements in the store.
|
||||||
|
auto size() const noexcept -> size_t { return data_.size(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|||||||
3579
include/fmt/base.h
3579
include/fmt/base.h
File diff suppressed because it is too large
Load Diff
1162
include/fmt/chrono.h
1162
include/fmt/chrono.h
File diff suppressed because it is too large
Load Diff
@ -155,7 +155,7 @@ enum class color : uint32_t {
|
|||||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||||
}; // enum class color
|
}; // enum class color
|
||||||
|
|
||||||
enum class terminal_color : uint8_t {
|
enum class terminal_color : uint8_t {
|
||||||
black = 30,
|
black = 30,
|
||||||
@ -190,11 +190,11 @@ enum class emphasis : uint8_t {
|
|||||||
// rgb is a struct for red, green and blue colors.
|
// rgb is a struct for red, green and blue colors.
|
||||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||||
struct rgb {
|
struct rgb {
|
||||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
constexpr rgb() : r(0), g(0), b(0) {}
|
||||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
constexpr rgb(uint32_t hex)
|
||||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||||
FMT_CONSTEXPR rgb(color hex)
|
constexpr rgb(color hex)
|
||||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||||
g((uint32_t(hex) >> 8) & 0xFF),
|
g((uint32_t(hex) >> 8) & 0xFF),
|
||||||
b(uint32_t(hex) & 0xFF) {}
|
b(uint32_t(hex) & 0xFF) {}
|
||||||
@ -205,97 +205,135 @@ struct rgb {
|
|||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// color is a struct of either a rgb color or a terminal color.
|
// A bit-packed variant of an RGB color, a terminal color, or unset color.
|
||||||
|
// see text_style for the bit-packing scheme.
|
||||||
struct color_type {
|
struct color_type {
|
||||||
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
|
constexpr color_type() noexcept = default;
|
||||||
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
|
constexpr color_type(color rgb_color) noexcept
|
||||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
|
||||||
|
constexpr color_type(rgb rgb_color) noexcept
|
||||||
|
: color_type(static_cast<color>(
|
||||||
|
(static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||||
|
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
|
||||||
|
constexpr color_type(terminal_color term_color) noexcept
|
||||||
|
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
|
||||||
|
|
||||||
|
constexpr auto is_terminal_color() const noexcept -> bool {
|
||||||
|
return (value_ & (1 << 25)) != 0;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
|
|
||||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
constexpr auto value() const noexcept -> uint32_t {
|
||||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
return value_ & 0xFFFFFF;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
|
|
||||||
: is_rgb(), value{} {
|
constexpr color_type(uint32_t value) noexcept : value_(value) {}
|
||||||
value.term_color = static_cast<uint8_t>(term_color);
|
|
||||||
}
|
uint32_t value_ = 0;
|
||||||
bool is_rgb;
|
|
||||||
union color_union {
|
|
||||||
uint8_t term_color;
|
|
||||||
uint32_t rgb_color;
|
|
||||||
} value;
|
|
||||||
};
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
/// A text style consisting of foreground and background colors and emphasis.
|
/// A text style consisting of foreground and background colors and emphasis.
|
||||||
class text_style {
|
class text_style {
|
||||||
|
// The information is packed as follows:
|
||||||
|
// ┌──┐
|
||||||
|
// │ 0│─┐
|
||||||
|
// │..│ ├── foreground color value
|
||||||
|
// │23│─┘
|
||||||
|
// ├──┤
|
||||||
|
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
|
||||||
|
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
|
||||||
|
// ├──┤
|
||||||
|
// │26│──── overflow bit, always zero (see below)
|
||||||
|
// ├──┤
|
||||||
|
// │27│─┐
|
||||||
|
// │..│ │
|
||||||
|
// │50│ │
|
||||||
|
// ├──┤ │
|
||||||
|
// │51│ ├── background color (same format as the foreground color)
|
||||||
|
// │52│ │
|
||||||
|
// ├──┤ │
|
||||||
|
// │53│─┘
|
||||||
|
// ├──┤
|
||||||
|
// │54│─┐
|
||||||
|
// │..│ ├── emphases
|
||||||
|
// │61│─┘
|
||||||
|
// ├──┤
|
||||||
|
// │62│─┬── unused
|
||||||
|
// │63│─┘
|
||||||
|
// └──┘
|
||||||
|
// The overflow bits are there to make operator|= efficient.
|
||||||
|
// When ORing, we must throw if, for either the foreground or background,
|
||||||
|
// one style specifies a terminal color and the other specifies any color
|
||||||
|
// (terminal or RGB); in other words, if one discriminator is 11 and the
|
||||||
|
// other is 11 or 01.
|
||||||
|
//
|
||||||
|
// We do that check by adding the styles. Consider what adding does to each
|
||||||
|
// possible pair of discriminators:
|
||||||
|
// 00 + 00 = 000
|
||||||
|
// 01 + 00 = 001
|
||||||
|
// 11 + 00 = 011
|
||||||
|
// 01 + 01 = 010
|
||||||
|
// 11 + 01 = 100 (!!)
|
||||||
|
// 11 + 11 = 110 (!!)
|
||||||
|
// In the last two cases, the ones we want to catch, the third bit——the
|
||||||
|
// overflow bit——is set. Bingo.
|
||||||
|
//
|
||||||
|
// We must take into account the possible carry bit from the bits
|
||||||
|
// before the discriminator. The only potentially problematic case is
|
||||||
|
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
|
||||||
|
// bit is impossible in that case, because 00 (unset color) means the
|
||||||
|
// 24 bits that precede the discriminator are all zero.
|
||||||
|
//
|
||||||
|
// This test can be applied to both colors simultaneously.
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
|
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
|
||||||
: set_foreground_color(), set_background_color(), ems(em) {}
|
: style_(static_cast<uint64_t>(em) << 54) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
|
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
|
||||||
if (!set_foreground_color) {
|
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
|
||||||
set_foreground_color = rhs.set_foreground_color;
|
report_error("can't OR a terminal color");
|
||||||
foreground_color = rhs.foreground_color;
|
style_ |= rhs.style_;
|
||||||
} else if (rhs.set_foreground_color) {
|
|
||||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
|
||||||
report_error("can't OR a terminal color");
|
|
||||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!set_background_color) {
|
|
||||||
set_background_color = rhs.set_background_color;
|
|
||||||
background_color = rhs.background_color;
|
|
||||||
} else if (rhs.set_background_color) {
|
|
||||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
|
||||||
report_error("can't OR a terminal color");
|
|
||||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
|
||||||
static_cast<uint8_t>(rhs.ems));
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
|
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
|
||||||
-> text_style {
|
-> text_style {
|
||||||
return lhs |= rhs;
|
return lhs |= rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
|
||||||
|
return style_ == rhs.style_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
|
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
|
||||||
return set_foreground_color;
|
return (style_ & (1 << 24)) != 0;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
|
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
|
||||||
return set_background_color;
|
return (style_ & (1ULL << 51)) != 0;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
|
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
|
||||||
return static_cast<uint8_t>(ems) != 0;
|
return (style_ >> 54) != 0;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
|
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
|
||||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||||
return foreground_color;
|
return style_ & 0x3FFFFFF;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
|
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
|
||||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||||
return background_color;
|
return (style_ >> 27) & 0x3FFFFFF;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
|
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
|
||||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||||
return ems;
|
return static_cast<emphasis>(style_ >> 54);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
|
||||||
detail::color_type text_color) noexcept
|
|
||||||
: set_foreground_color(), set_background_color(), ems() {
|
|
||||||
if (is_foreground) {
|
|
||||||
foreground_color = text_color;
|
|
||||||
set_foreground_color = true;
|
|
||||||
} else {
|
|
||||||
background_color = text_color;
|
|
||||||
set_background_color = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
|
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
|
||||||
-> text_style;
|
-> text_style;
|
||||||
@ -303,23 +341,19 @@ class text_style {
|
|||||||
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
|
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
|
||||||
-> text_style;
|
-> text_style;
|
||||||
|
|
||||||
detail::color_type foreground_color;
|
uint64_t style_ = 0;
|
||||||
detail::color_type background_color;
|
|
||||||
bool set_foreground_color;
|
|
||||||
bool set_background_color;
|
|
||||||
emphasis ems;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates a text style from the foreground (text) color.
|
/// Creates a text style from the foreground (text) color.
|
||||||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
|
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
|
||||||
-> text_style {
|
-> text_style {
|
||||||
return text_style(true, foreground);
|
return foreground.value_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a text style from the background color.
|
/// Creates a text style from the background color.
|
||||||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
|
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
|
||||||
-> text_style {
|
-> text_style {
|
||||||
return text_style(false, background);
|
return static_cast<uint64_t>(background.value_) << 27;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
||||||
@ -330,41 +364,39 @@ FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename Char> struct ansi_color_escape {
|
template <typename Char> struct ansi_color_escape {
|
||||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
FMT_CONSTEXPR ansi_color_escape(color_type text_color,
|
||||||
const char* esc) noexcept {
|
const char* esc) noexcept {
|
||||||
// If we have a terminal color, we need to output another escape code
|
// If we have a terminal color, we need to output another escape code
|
||||||
// sequence.
|
// sequence.
|
||||||
if (!text_color.is_rgb) {
|
if (text_color.is_terminal_color()) {
|
||||||
bool is_background = esc == string_view("\x1b[48;2;");
|
bool is_background = esc == string_view("\x1b[48;2;");
|
||||||
uint32_t value = text_color.value.term_color;
|
uint32_t value = text_color.value();
|
||||||
// Background ASCII codes are the same as the foreground ones but with
|
// Background ASCII codes are the same as the foreground ones but with
|
||||||
// 10 more.
|
// 10 more.
|
||||||
if (is_background) value += 10u;
|
if (is_background) value += 10u;
|
||||||
|
|
||||||
size_t index = 0;
|
buffer[size++] = static_cast<Char>('\x1b');
|
||||||
buffer[index++] = static_cast<Char>('\x1b');
|
buffer[size++] = static_cast<Char>('[');
|
||||||
buffer[index++] = static_cast<Char>('[');
|
|
||||||
|
|
||||||
if (value >= 100u) {
|
if (value >= 100u) {
|
||||||
buffer[index++] = static_cast<Char>('1');
|
buffer[size++] = static_cast<Char>('1');
|
||||||
value %= 100u;
|
value %= 100u;
|
||||||
}
|
}
|
||||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
buffer[size++] = static_cast<Char>('0' + value / 10u);
|
||||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
buffer[size++] = static_cast<Char>('0' + value % 10u);
|
||||||
|
|
||||||
buffer[index++] = static_cast<Char>('m');
|
buffer[size++] = static_cast<Char>('m');
|
||||||
buffer[index++] = static_cast<Char>('\0');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
buffer[i] = static_cast<Char>(esc[i]);
|
buffer[i] = static_cast<Char>(esc[i]);
|
||||||
}
|
}
|
||||||
rgb color(text_color.value.rgb_color);
|
rgb color(text_color.value());
|
||||||
to_esc(color.r, buffer + 7, ';');
|
to_esc(color.r, buffer + 7, ';');
|
||||||
to_esc(color.g, buffer + 11, ';');
|
to_esc(color.g, buffer + 11, ';');
|
||||||
to_esc(color.b, buffer + 15, 'm');
|
to_esc(color.b, buffer + 15, 'm');
|
||||||
buffer[19] = static_cast<Char>(0);
|
size = 19;
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
|
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
|
||||||
uint8_t em_codes[num_emphases] = {};
|
uint8_t em_codes[num_emphases] = {};
|
||||||
@ -377,26 +409,28 @@ template <typename Char> struct ansi_color_escape {
|
|||||||
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
|
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
|
||||||
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
|
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
|
||||||
|
|
||||||
size_t index = 0;
|
buffer[size++] = static_cast<Char>('\x1b');
|
||||||
|
buffer[size++] = static_cast<Char>('[');
|
||||||
|
|
||||||
for (size_t i = 0; i < num_emphases; ++i) {
|
for (size_t i = 0; i < num_emphases; ++i) {
|
||||||
if (!em_codes[i]) continue;
|
if (!em_codes[i]) continue;
|
||||||
buffer[index++] = static_cast<Char>('\x1b');
|
buffer[size++] = static_cast<Char>('0' + em_codes[i]);
|
||||||
buffer[index++] = static_cast<Char>('[');
|
buffer[size++] = static_cast<Char>(';');
|
||||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
|
||||||
buffer[index++] = static_cast<Char>('m');
|
|
||||||
}
|
}
|
||||||
buffer[index++] = static_cast<Char>(0);
|
|
||||||
|
buffer[size - 1] = static_cast<Char>('m');
|
||||||
}
|
}
|
||||||
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
|
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
|
||||||
|
|
||||||
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
|
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
|
||||||
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
|
FMT_CONSTEXPR auto end() const noexcept -> const Char* {
|
||||||
return buffer + basic_string_view<Char>(buffer).size();
|
return buffer + size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t num_emphases = 8;
|
static constexpr size_t num_emphases = 8;
|
||||||
Char buffer[7u + 3u * num_emphases + 1u];
|
Char buffer[7u + 4u * num_emphases] = {};
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||||
char delimiter) noexcept {
|
char delimiter) noexcept {
|
||||||
@ -412,13 +446,13 @@ template <typename Char> struct ansi_color_escape {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
|
FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
|
||||||
-> ansi_color_escape<Char> {
|
-> ansi_color_escape<Char> {
|
||||||
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
|
FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
|
||||||
-> ansi_color_escape<Char> {
|
-> ansi_color_escape<Char> {
|
||||||
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||||
}
|
}
|
||||||
@ -434,40 +468,33 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
|||||||
buffer.append(reset_color.begin(), reset_color.end());
|
buffer.append(reset_color.begin(), reset_color.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct styled_arg : detail::view {
|
template <typename T> struct styled_arg : view {
|
||||||
const T& value;
|
const T& value;
|
||||||
text_style style;
|
text_style style;
|
||||||
styled_arg(const T& v, text_style s) : value(v), style(s) {}
|
styled_arg(const T& v, text_style s) : value(v), style(s) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void vformat_to(
|
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
|
||||||
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str,
|
basic_format_args<buffered_context<Char>> args) {
|
||||||
basic_format_args<buffered_context<type_identity_t<Char>>> args) {
|
|
||||||
bool has_style = false;
|
|
||||||
if (ts.has_emphasis()) {
|
if (ts.has_emphasis()) {
|
||||||
has_style = true;
|
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
|
||||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
|
||||||
buf.append(emphasis.begin(), emphasis.end());
|
buf.append(emphasis.begin(), emphasis.end());
|
||||||
}
|
}
|
||||||
if (ts.has_foreground()) {
|
if (ts.has_foreground()) {
|
||||||
has_style = true;
|
auto foreground = make_foreground_color<Char>(ts.get_foreground());
|
||||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
|
||||||
buf.append(foreground.begin(), foreground.end());
|
buf.append(foreground.begin(), foreground.end());
|
||||||
}
|
}
|
||||||
if (ts.has_background()) {
|
if (ts.has_background()) {
|
||||||
has_style = true;
|
auto background = make_background_color<Char>(ts.get_background());
|
||||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
|
||||||
buf.append(background.begin(), background.end());
|
buf.append(background.begin(), background.end());
|
||||||
}
|
}
|
||||||
detail::vformat_to(buf, format_str, args, {});
|
vformat_to(buf, fmt, args);
|
||||||
if (has_style) detail::reset_color<Char>(buf);
|
if (ts != text_style()) reset_color<Char>(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
|
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
|
||||||
format_args args) {
|
|
||||||
auto buf = memory_buffer();
|
auto buf = memory_buffer();
|
||||||
detail::vformat_to(buf, ts, fmt, args);
|
detail::vformat_to(buf, ts, fmt, args);
|
||||||
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
|
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
|
||||||
@ -483,9 +510,8 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
|
|||||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||||
*/
|
*/
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
|
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
|
||||||
T&&... args) {
|
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
|
||||||
vprint(f, ts, fmt, fmt::make_format_args(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -498,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt,
|
|||||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||||
*/
|
*/
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
|
void print(text_style ts, format_string<T...> fmt, T&&... args) {
|
||||||
return print(stdout, ts, fmt, std::forward<T>(args)...);
|
return print(stdout, ts, fmt, std::forward<T>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
|
inline auto vformat(text_style ts, string_view fmt, format_args args)
|
||||||
-> std::string {
|
-> std::string {
|
||||||
auto buf = memory_buffer();
|
auto buf = memory_buffer();
|
||||||
detail::vformat_to(buf, ts, fmt, args);
|
detail::vformat_to(buf, ts, fmt, args);
|
||||||
@ -522,16 +548,16 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
|
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
|
||||||
-> std::string {
|
-> std::string {
|
||||||
return fmt::vformat(ts, fmt, fmt::make_format_args(args...));
|
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats a string with the given text_style and writes the output to `out`.
|
/// Formats a string with the given text_style and writes the output to `out`.
|
||||||
template <typename OutputIt,
|
template <typename OutputIt,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||||
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
|
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
|
||||||
format_args args) -> OutputIt {
|
-> OutputIt {
|
||||||
auto&& buf = detail::get_buffer<char>(out);
|
auto&& buf = detail::get_buffer<char>(out);
|
||||||
detail::vformat_to(buf, ts, fmt, args);
|
detail::vformat_to(buf, ts, fmt, args);
|
||||||
return detail::get_iterator(buf, out);
|
return detail::get_iterator(buf, out);
|
||||||
@ -549,9 +575,9 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
|
|||||||
*/
|
*/
|
||||||
template <typename OutputIt, typename... T,
|
template <typename OutputIt, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||||
inline auto format_to(OutputIt out, const text_style& ts,
|
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
|
||||||
format_string<T...> fmt, T&&... args) -> OutputIt {
|
T&&... args) -> OutputIt {
|
||||||
return vformat_to(out, ts, fmt, fmt::make_format_args(args...));
|
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
@ -560,31 +586,30 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
|
|||||||
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
|
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
const auto& ts = arg.style;
|
const auto& ts = arg.style;
|
||||||
const auto& value = arg.value;
|
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
|
|
||||||
bool has_style = false;
|
bool has_style = false;
|
||||||
if (ts.has_emphasis()) {
|
if (ts.has_emphasis()) {
|
||||||
has_style = true;
|
has_style = true;
|
||||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||||
out = std::copy(emphasis.begin(), emphasis.end(), out);
|
out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
|
||||||
}
|
}
|
||||||
if (ts.has_foreground()) {
|
if (ts.has_foreground()) {
|
||||||
has_style = true;
|
has_style = true;
|
||||||
auto foreground =
|
auto foreground =
|
||||||
detail::make_foreground_color<Char>(ts.get_foreground());
|
detail::make_foreground_color<Char>(ts.get_foreground());
|
||||||
out = std::copy(foreground.begin(), foreground.end(), out);
|
out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
|
||||||
}
|
}
|
||||||
if (ts.has_background()) {
|
if (ts.has_background()) {
|
||||||
has_style = true;
|
has_style = true;
|
||||||
auto background =
|
auto background =
|
||||||
detail::make_background_color<Char>(ts.get_background());
|
detail::make_background_color<Char>(ts.get_background());
|
||||||
out = std::copy(background.begin(), background.end(), out);
|
out = detail::copy<Char>(background.begin(), background.end(), out);
|
||||||
}
|
}
|
||||||
out = formatter<T, Char>::format(value, ctx);
|
out = formatter<T, Char>::format(arg.value, ctx);
|
||||||
if (has_style) {
|
if (has_style) {
|
||||||
auto reset_color = string_view("\x1b[0m");
|
auto reset_color = string_view("\x1b[0m");
|
||||||
out = std::copy(reset_color.begin(), reset_color.end(), out);
|
out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,17 +15,10 @@
|
|||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
// A compile-time string which is compiled into fast formatting code.
|
// A compile-time string which is compiled into fast formatting code.
|
||||||
FMT_EXPORT class compiled_string {};
|
class compiled_string {};
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
template <typename T, typename InputIt>
|
|
||||||
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
|
|
||||||
-> counting_iterator {
|
|
||||||
return it + (end - begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S>
|
template <typename S>
|
||||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||||
@ -42,34 +35,47 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
|||||||
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||||
*/
|
*/
|
||||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||||
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit)
|
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
|
||||||
#else
|
#else
|
||||||
# define FMT_COMPILE(s) FMT_STRING(s)
|
# define FMT_COMPILE(s) FMT_STRING(s)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string literal into a format string that will be parsed at
|
||||||
|
* compile time and converted into efficient formatting code. Requires support
|
||||||
|
* for class types in constant template parameters (a C++20 feature).
|
||||||
|
*
|
||||||
|
* **Example**:
|
||||||
|
*
|
||||||
|
* // Converts 42 into std::string using the most efficient method and no
|
||||||
|
* // runtime format string processing.
|
||||||
|
* using namespace fmt::literals;
|
||||||
|
* std::string s = fmt::format("{}"_cf, 42);
|
||||||
|
*/
|
||||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||||
template <typename Char, size_t N,
|
inline namespace literals {
|
||||||
fmt::detail_exported::fixed_string<Char, N> Str>
|
template <detail::fixed_string Str> constexpr auto operator""_cf() {
|
||||||
struct udl_compiled_string : compiled_string {
|
return FMT_COMPILE(Str.data);
|
||||||
using char_type = Char;
|
}
|
||||||
explicit constexpr operator basic_string_view<char_type>() const {
|
} // namespace literals
|
||||||
return {Str.data, N - 1};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
FMT_END_EXPORT
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
template <typename T, typename... Tail>
|
template <typename T, typename... Tail>
|
||||||
auto first(const T& value, const Tail&...) -> const T& {
|
constexpr auto first(const T& value, const Tail&...) -> const T& {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||||
template <typename... Args> struct type_list {};
|
template <typename... T> struct type_list {};
|
||||||
|
|
||||||
// Returns a reference to the argument at index N from [first, rest...].
|
// Returns a reference to the argument at index N from [first, rest...].
|
||||||
template <int N, typename T, typename... Args>
|
template <int N, typename T, typename... Args>
|
||||||
constexpr const auto& get([[maybe_unused]] const T& first,
|
constexpr auto get([[maybe_unused]] const T& first,
|
||||||
[[maybe_unused]] const Args&... rest) {
|
[[maybe_unused]] const Args&... rest) -> const auto& {
|
||||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||||
if constexpr (N == 0)
|
if constexpr (N == 0)
|
||||||
return first;
|
return first;
|
||||||
@ -77,9 +83,32 @@ constexpr const auto& get([[maybe_unused]] const T& first,
|
|||||||
return detail::get<N - 1>(rest...);
|
return detail::get<N - 1>(rest...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||||
|
template <int N, typename T, typename... Args, typename Char>
|
||||||
|
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
|
||||||
|
if constexpr (is_static_named_arg<T>()) {
|
||||||
|
if (name == T::name) return N;
|
||||||
|
}
|
||||||
|
if constexpr (sizeof...(Args) > 0)
|
||||||
|
return get_arg_index_by_name<N + 1, Args...>(name);
|
||||||
|
(void)name; // Workaround an MSVC bug about "unused" parameter.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
template <typename... Args, typename Char>
|
||||||
|
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
|
||||||
|
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||||
|
if constexpr (sizeof...(Args) > 0)
|
||||||
|
return get_arg_index_by_name<0, Args...>(name);
|
||||||
|
# endif
|
||||||
|
(void)name;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char, typename... Args>
|
template <typename Char, typename... Args>
|
||||||
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
|
||||||
type_list<Args...>) {
|
type_list<Args...>) -> int {
|
||||||
return get_arg_index_by_name<Args...>(name);
|
return get_arg_index_by_name<Args...>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,8 +128,8 @@ template <typename Char> struct text {
|
|||||||
basic_string_view<Char> data;
|
basic_string_view<Char> data;
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... T>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
|
||||||
return write<Char>(out, data);
|
return write<Char>(out, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -109,8 +138,8 @@ template <typename Char>
|
|||||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
|
||||||
size_t size) {
|
-> text<Char> {
|
||||||
return {{&s[pos], size}};
|
return {{&s[pos], size}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,8 +147,8 @@ template <typename Char> struct code_unit {
|
|||||||
Char value;
|
Char value;
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... T>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
|
||||||
*out++ = value;
|
*out++ = value;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -127,7 +156,7 @@ template <typename Char> struct code_unit {
|
|||||||
|
|
||||||
// This ensures that the argument type is convertible to `const T&`.
|
// This ensures that the argument type is convertible to `const T&`.
|
||||||
template <typename T, int N, typename... Args>
|
template <typename T, int N, typename... Args>
|
||||||
constexpr const T& get_arg_checked(const Args&... args) {
|
constexpr auto get_arg_checked(const Args&... args) -> const T& {
|
||||||
const auto& arg = detail::get<N>(args...);
|
const auto& arg = detail::get<N>(args...);
|
||||||
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||||
return arg.value;
|
return arg.value;
|
||||||
@ -140,17 +169,18 @@ template <typename Char>
|
|||||||
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||||
|
|
||||||
// A replacement field that refers to argument N.
|
// A replacement field that refers to argument N.
|
||||||
template <typename Char, typename T, int N> struct field {
|
template <typename Char, typename V, int N> struct field {
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... T>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
|
||||||
const T& arg = get_arg_checked<T, N>(args...);
|
const V& arg = get_arg_checked<V, N>(args...);
|
||||||
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
|
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
|
||||||
auto s = basic_string_view<Char>(arg);
|
auto s = basic_string_view<Char>(arg);
|
||||||
return copy<Char>(s.begin(), s.end(), out);
|
return copy<Char>(s.begin(), s.end(), out);
|
||||||
|
} else {
|
||||||
|
return write<Char>(out, arg);
|
||||||
}
|
}
|
||||||
return write<Char>(out, arg);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,10 +193,10 @@ template <typename Char> struct runtime_named_field {
|
|||||||
basic_string_view<Char> name;
|
basic_string_view<Char> name;
|
||||||
|
|
||||||
template <typename OutputIt, typename T>
|
template <typename OutputIt, typename T>
|
||||||
constexpr static bool try_format_argument(
|
constexpr static auto try_format_argument(
|
||||||
OutputIt& out,
|
OutputIt& out,
|
||||||
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
||||||
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
|
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
|
||||||
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
||||||
if (arg_name == arg.name) {
|
if (arg_name == arg.name) {
|
||||||
out = write<Char>(out, arg.value);
|
out = write<Char>(out, arg.value);
|
||||||
@ -176,8 +206,8 @@ template <typename Char> struct runtime_named_field {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... T>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
|
||||||
bool found = (try_format_argument(out, name, args) || ...);
|
bool found = (try_format_argument(out, name, args) || ...);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
FMT_THROW(format_error("argument with specified name is not found"));
|
FMT_THROW(format_error("argument with specified name is not found"));
|
||||||
@ -190,17 +220,17 @@ template <typename Char>
|
|||||||
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
||||||
|
|
||||||
// A replacement field that refers to argument N and has format specifiers.
|
// A replacement field that refers to argument N and has format specifiers.
|
||||||
template <typename Char, typename T, int N> struct spec_field {
|
template <typename Char, typename V, int N> struct spec_field {
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
formatter<T, Char> fmt;
|
formatter<V, Char> fmt;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... T>
|
||||||
constexpr FMT_INLINE OutputIt format(OutputIt out,
|
constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
|
||||||
const Args&... args) const {
|
-> OutputIt {
|
||||||
const auto& vargs =
|
const auto& vargs =
|
||||||
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||||
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||||
return fmt.format(get_arg_checked<T, N>(args...), ctx);
|
return fmt.format(get_arg_checked<V, N>(args...), ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -212,8 +242,8 @@ template <typename L, typename R> struct concat {
|
|||||||
R rhs;
|
R rhs;
|
||||||
using char_type = typename L::char_type;
|
using char_type = typename L::char_type;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... T>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
|
||||||
out = lhs.format(out, args...);
|
out = lhs.format(out, args...);
|
||||||
return rhs.format(out, args...);
|
return rhs.format(out, args...);
|
||||||
}
|
}
|
||||||
@ -223,14 +253,14 @@ template <typename L, typename R>
|
|||||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||||
|
|
||||||
template <typename L, typename R>
|
template <typename L, typename R>
|
||||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
|
||||||
return {lhs, rhs};
|
return {lhs, rhs};
|
||||||
}
|
}
|
||||||
|
|
||||||
struct unknown_format {};
|
struct unknown_format {};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
|
||||||
for (size_t size = str.size(); pos != size; ++pos) {
|
for (size_t size = str.size(); pos != size; ++pos) {
|
||||||
if (str[pos] == '{' || str[pos] == '}') break;
|
if (str[pos] == '{' || str[pos] == '}') break;
|
||||||
}
|
}
|
||||||
@ -263,8 +293,8 @@ template <typename T, typename Char> struct parse_specs_result {
|
|||||||
enum { manual_indexing_id = -1 };
|
enum { manual_indexing_id = -1 };
|
||||||
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
|
||||||
size_t pos, int next_arg_id) {
|
int next_arg_id) -> parse_specs_result<T, Char> {
|
||||||
str.remove_prefix(pos);
|
str.remove_prefix(pos);
|
||||||
auto ctx =
|
auto ctx =
|
||||||
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
|
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
|
||||||
@ -275,32 +305,36 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char> struct arg_id_handler {
|
template <typename Char> struct arg_id_handler {
|
||||||
|
arg_id_kind kind;
|
||||||
arg_ref<Char> arg_id;
|
arg_ref<Char> arg_id;
|
||||||
|
|
||||||
constexpr int on_auto() {
|
constexpr auto on_auto() -> int {
|
||||||
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
constexpr int on_index(int id) {
|
constexpr auto on_index(int id) -> int {
|
||||||
|
kind = arg_id_kind::index;
|
||||||
arg_id = arg_ref<Char>(id);
|
arg_id = arg_ref<Char>(id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
constexpr int on_name(basic_string_view<Char> id) {
|
constexpr auto on_name(basic_string_view<Char> id) -> int {
|
||||||
|
kind = arg_id_kind::name;
|
||||||
arg_id = arg_ref<Char>(id);
|
arg_id = arg_ref<Char>(id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char> struct parse_arg_id_result {
|
template <typename Char> struct parse_arg_id_result {
|
||||||
|
arg_id_kind kind;
|
||||||
arg_ref<Char> arg_id;
|
arg_ref<Char> arg_id;
|
||||||
const Char* arg_id_end;
|
const Char* arg_id_end;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <int ID, typename Char>
|
template <int ID, typename Char>
|
||||||
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
||||||
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
|
auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
|
||||||
auto arg_id_end = parse_arg_id(begin, end, handler);
|
auto arg_id_end = parse_arg_id(begin, end, handler);
|
||||||
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
|
return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename Enable = void> struct field_type {
|
template <typename T, typename Enable = void> struct field_type {
|
||||||
@ -363,18 +397,18 @@ constexpr auto compile_format_string(S fmt) {
|
|||||||
constexpr char_type c =
|
constexpr char_type c =
|
||||||
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
||||||
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
||||||
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
|
if constexpr (arg_id_result.kind == arg_id_kind::index) {
|
||||||
static_assert(
|
static_assert(
|
||||||
ID == manual_indexing_id || ID == 0,
|
ID == manual_indexing_id || ID == 0,
|
||||||
"cannot switch from automatic to manual argument indexing");
|
"cannot switch from automatic to manual argument indexing");
|
||||||
constexpr auto arg_index = arg_id_result.arg_id.val.index;
|
constexpr auto arg_index = arg_id_result.arg_id.index;
|
||||||
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
||||||
Args, arg_id_end_pos,
|
Args, arg_id_end_pos,
|
||||||
arg_index, manual_indexing_id>(
|
arg_index, manual_indexing_id>(
|
||||||
fmt);
|
fmt);
|
||||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
|
||||||
constexpr auto arg_index =
|
constexpr auto arg_index =
|
||||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
|
||||||
if constexpr (arg_index >= 0) {
|
if constexpr (arg_index >= 0) {
|
||||||
constexpr auto next_id =
|
constexpr auto next_id =
|
||||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||||
@ -383,8 +417,7 @@ constexpr auto compile_format_string(S fmt) {
|
|||||||
arg_index, next_id>(fmt);
|
arg_index, next_id>(fmt);
|
||||||
} else if constexpr (c == '}') {
|
} else if constexpr (c == '}') {
|
||||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
|
||||||
fmt);
|
|
||||||
} else if constexpr (c == ':') {
|
} else if constexpr (c == ':') {
|
||||||
return unknown_format(); // no type info for specs parsing
|
return unknown_format(); // no type info for specs parsing
|
||||||
}
|
}
|
||||||
@ -405,7 +438,7 @@ constexpr auto compile_format_string(S fmt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args, typename S,
|
template <typename... Args, typename S,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
constexpr auto compile(S fmt) {
|
constexpr auto compile(S fmt) {
|
||||||
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
|
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
|
||||||
if constexpr (str.size() == 0) {
|
if constexpr (str.size() == 0) {
|
||||||
@ -423,27 +456,28 @@ FMT_BEGIN_EXPORT
|
|||||||
|
|
||||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args,
|
template <typename CompiledFormat, typename... T,
|
||||||
typename Char = typename CompiledFormat::char_type,
|
typename Char = typename CompiledFormat::char_type,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const T&... args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
auto s = std::basic_string<Char>();
|
auto s = std::basic_string<Char>();
|
||||||
cf.format(std::back_inserter(s), args...);
|
cf.format(std::back_inserter(s), args...);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename CompiledFormat, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const T&... args) -> OutputIt {
|
||||||
return cf.format(out, args...);
|
return cf.format(out, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
|
||||||
Args&&... args) {
|
-> std::basic_string<typename S::char_type> {
|
||||||
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||||
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||||
@ -456,72 +490,97 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
constexpr auto compiled = detail::compile<Args...>(S());
|
constexpr auto compiled = detail::compile<T...>(S());
|
||||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||||
detail::unknown_format>()) {
|
detail::unknown_format>()) {
|
||||||
return fmt::format(
|
return fmt::format(
|
||||||
static_cast<basic_string_view<typename S::char_type>>(S()),
|
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||||
std::forward<Args>(args)...);
|
std::forward<T>(args)...);
|
||||||
} else {
|
} else {
|
||||||
return fmt::format(compiled, std::forward<Args>(args)...);
|
return fmt::format(compiled, std::forward<T>(args)...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
template <typename OutputIt, typename S, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
|
||||||
constexpr auto compiled = detail::compile<Args...>(S());
|
constexpr auto compiled = detail::compile<T...>(S());
|
||||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||||
detail::unknown_format>()) {
|
detail::unknown_format>()) {
|
||||||
return fmt::format_to(
|
return fmt::format_to(
|
||||||
out, static_cast<basic_string_view<typename S::char_type>>(S()),
|
out, static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||||
std::forward<Args>(args)...);
|
std::forward<T>(args)...);
|
||||||
} else {
|
} else {
|
||||||
return fmt::format_to(out, compiled, std::forward<Args>(args)...);
|
return fmt::format_to(out, compiled, std::forward<T>(args)...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
template <typename OutputIt, typename S, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
|
auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||||
-> format_to_n_result<OutputIt> {
|
-> format_to_n_result<OutputIt> {
|
||||||
using traits = detail::fixed_buffer_traits;
|
using traits = detail::fixed_buffer_traits;
|
||||||
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||||
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);
|
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
|
||||||
return {buf.out(), buf.count()};
|
return {buf.out(), buf.count()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
|
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||||
-> size_t {
|
auto buf = detail::counting_buffer<>();
|
||||||
return fmt::format_to(detail::counting_iterator(), fmt, args...).count();
|
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
|
||||||
|
return buf.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
void print(std::FILE* f, const S& fmt, const Args&... args) {
|
void print(std::FILE* f, const S& fmt, T&&... args) {
|
||||||
memory_buffer buffer;
|
auto buf = memory_buffer();
|
||||||
fmt::format_to(std::back_inserter(buffer), fmt, args...);
|
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
|
||||||
detail::print(f, {buffer.data(), buffer.size()});
|
detail::print(f, {buf.data(), buf.size()});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
void print(const S& fmt, const Args&... args) {
|
void print(const S& fmt, T&&... args) {
|
||||||
print(stdout, fmt, args...);
|
print(stdout, fmt, std::forward<T>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
template <size_t N> class static_format_result {
|
||||||
inline namespace literals {
|
private:
|
||||||
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
|
char data[N];
|
||||||
using char_t = remove_cvref_t<decltype(Str.data[0])>;
|
|
||||||
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
|
public:
|
||||||
Str>();
|
template <typename S, typename... T,
|
||||||
}
|
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||||
} // namespace literals
|
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
|
||||||
#endif
|
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
|
||||||
|
FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats arguments according to the format string `fmt_str` and produces
|
||||||
|
* a string of the exact required size at compile time. Both the format string
|
||||||
|
* and the arguments must be compile-time expressions.
|
||||||
|
*
|
||||||
|
* The resulting string can be accessed as a C string via `c_str()` or as
|
||||||
|
* a `fmt::string_view` via `str()`.
|
||||||
|
*
|
||||||
|
* **Example**:
|
||||||
|
*
|
||||||
|
* // Produces the static string "42" at compile time.
|
||||||
|
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
|
||||||
|
* const char* s = result.c_str();
|
||||||
|
*/
|
||||||
|
#define FMT_STATIC_FORMAT(fmt_str, ...) \
|
||||||
|
fmt::static_format_result< \
|
||||||
|
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
|
||||||
|
FMT_COMPILE(fmt_str), __VA_ARGS__)
|
||||||
|
|
||||||
FMT_END_EXPORT
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
3009
include/fmt/format.h
3009
include/fmt/format.h
File diff suppressed because it is too large
Load Diff
110
include/fmt/os.h
110
include/fmt/os.h
@ -29,7 +29,8 @@
|
|||||||
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||||
defined(__linux__)) && \
|
defined(__linux__)) && \
|
||||||
(!defined(WINAPI_FAMILY) || \
|
(!defined(WINAPI_FAMILY) || \
|
||||||
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
|
||||||
|
!defined(__wasm__)
|
||||||
# include <fcntl.h> // for O_RDONLY
|
# include <fcntl.h> // for O_RDONLY
|
||||||
# define FMT_USE_FCNTL 1
|
# define FMT_USE_FCNTL 1
|
||||||
# else
|
# else
|
||||||
@ -118,7 +119,7 @@ FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
|||||||
const char* message) noexcept;
|
const char* message) noexcept;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
|
||||||
format_args args);
|
format_args args);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,10 +136,9 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
|||||||
* **Example**:
|
* **Example**:
|
||||||
*
|
*
|
||||||
* // This throws a system_error with the description
|
* // This throws a system_error with the description
|
||||||
* // cannot open file 'madeup': The system cannot find the file
|
* // cannot open file 'foo': The system cannot find the file specified.
|
||||||
* specified.
|
* // or similar (system message may vary) if the file doesn't exist.
|
||||||
* // or similar (system message may vary).
|
* const char *filename = "foo";
|
||||||
* const char *filename = "madeup";
|
|
||||||
* LPOFSTRUCT of = LPOFSTRUCT();
|
* LPOFSTRUCT of = LPOFSTRUCT();
|
||||||
* HFILE file = OpenFile(filename, &of, OF_READ);
|
* HFILE file = OpenFile(filename, &of, OF_READ);
|
||||||
* if (file == HFILE_ERROR) {
|
* if (file == HFILE_ERROR) {
|
||||||
@ -146,10 +146,10 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
|||||||
* "cannot open file '{}'", filename);
|
* "cannot open file '{}'", filename);
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... T>
|
||||||
std::system_error windows_error(int error_code, string_view message,
|
auto windows_error(int error_code, string_view message, const T&... args)
|
||||||
const Args&... args) {
|
-> std::system_error {
|
||||||
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
return vwindows_error(error_code, message, vargs<T...>{{args...}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reports a Windows error without throwing an exception.
|
// Reports a Windows error without throwing an exception.
|
||||||
@ -161,14 +161,6 @@ inline auto system_category() noexcept -> const std::error_category& {
|
|||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
// std::system is not available on some platforms such as iOS (#2248).
|
|
||||||
#ifdef __OSX__
|
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
|
||||||
void say(const S& format_str, Args&&... args) {
|
|
||||||
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// A buffered file.
|
// A buffered file.
|
||||||
class buffered_file {
|
class buffered_file {
|
||||||
private:
|
private:
|
||||||
@ -176,24 +168,24 @@ class buffered_file {
|
|||||||
|
|
||||||
friend class file;
|
friend class file;
|
||||||
|
|
||||||
explicit buffered_file(FILE* f) : file_(f) {}
|
inline explicit buffered_file(FILE* f) : file_(f) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
buffered_file(const buffered_file&) = delete;
|
buffered_file(const buffered_file&) = delete;
|
||||||
void operator=(const buffered_file&) = delete;
|
void operator=(const buffered_file&) = delete;
|
||||||
|
|
||||||
// Constructs a buffered_file object which doesn't represent any file.
|
// Constructs a buffered_file object which doesn't represent any file.
|
||||||
buffered_file() noexcept : file_(nullptr) {}
|
inline buffered_file() noexcept : file_(nullptr) {}
|
||||||
|
|
||||||
// Destroys the object closing the file it represents if any.
|
// Destroys the object closing the file it represents if any.
|
||||||
FMT_API ~buffered_file() noexcept;
|
FMT_API ~buffered_file() noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
|
inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
|
||||||
other.file_ = nullptr;
|
other.file_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto operator=(buffered_file&& other) -> buffered_file& {
|
inline auto operator=(buffered_file&& other) -> buffered_file& {
|
||||||
close();
|
close();
|
||||||
file_ = other.file_;
|
file_ = other.file_;
|
||||||
other.file_ = nullptr;
|
other.file_ = nullptr;
|
||||||
@ -207,13 +199,13 @@ class buffered_file {
|
|||||||
FMT_API void close();
|
FMT_API void close();
|
||||||
|
|
||||||
// Returns the pointer to a FILE object representing this file.
|
// Returns the pointer to a FILE object representing this file.
|
||||||
auto get() const noexcept -> FILE* { return file_; }
|
inline auto get() const noexcept -> FILE* { return file_; }
|
||||||
|
|
||||||
FMT_API auto descriptor() const -> int;
|
FMT_API auto descriptor() const -> int;
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
inline void print(string_view fmt, const T&... args) {
|
inline void print(string_view fmt, const T&... args) {
|
||||||
const auto& vargs = fmt::make_format_args(args...);
|
fmt::vargs<T...> vargs = {{args...}};
|
||||||
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
|
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
|
||||||
: fmt::vprint(file_, fmt, vargs);
|
: fmt::vprint(file_, fmt, vargs);
|
||||||
}
|
}
|
||||||
@ -248,7 +240,7 @@ class FMT_API file {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Constructs a file object which doesn't represent any file.
|
// Constructs a file object which doesn't represent any file.
|
||||||
file() noexcept : fd_(-1) {}
|
inline file() noexcept : fd_(-1) {}
|
||||||
|
|
||||||
// Opens a file and constructs a file object representing this file.
|
// Opens a file and constructs a file object representing this file.
|
||||||
file(cstring_view path, int oflag);
|
file(cstring_view path, int oflag);
|
||||||
@ -257,10 +249,10 @@ class FMT_API file {
|
|||||||
file(const file&) = delete;
|
file(const file&) = delete;
|
||||||
void operator=(const file&) = delete;
|
void operator=(const file&) = delete;
|
||||||
|
|
||||||
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
|
inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
|
||||||
|
|
||||||
// Move assignment is not noexcept because close may throw.
|
// Move assignment is not noexcept because close may throw.
|
||||||
auto operator=(file&& other) -> file& {
|
inline auto operator=(file&& other) -> file& {
|
||||||
close();
|
close();
|
||||||
fd_ = other.fd_;
|
fd_ = other.fd_;
|
||||||
other.fd_ = -1;
|
other.fd_ = -1;
|
||||||
@ -271,7 +263,7 @@ class FMT_API file {
|
|||||||
~file() noexcept;
|
~file() noexcept;
|
||||||
|
|
||||||
// Returns the file descriptor.
|
// Returns the file descriptor.
|
||||||
auto descriptor() const noexcept -> int { return fd_; }
|
inline auto descriptor() const noexcept -> int { return fd_; }
|
||||||
|
|
||||||
// Closes the file.
|
// Closes the file.
|
||||||
void close();
|
void close();
|
||||||
@ -324,9 +316,9 @@ auto getpagesize() -> long;
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
struct buffer_size {
|
struct buffer_size {
|
||||||
buffer_size() = default;
|
constexpr buffer_size() = default;
|
||||||
size_t value = 0;
|
size_t value = 0;
|
||||||
auto operator=(size_t val) const -> buffer_size {
|
FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
|
||||||
auto bs = buffer_size();
|
auto bs = buffer_size();
|
||||||
bs.value = val;
|
bs.value = val;
|
||||||
return bs;
|
return bs;
|
||||||
@ -337,7 +329,7 @@ struct ostream_params {
|
|||||||
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||||
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||||
|
|
||||||
ostream_params() {}
|
constexpr ostream_params() {}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||||
@ -358,59 +350,47 @@ struct ostream_params {
|
|||||||
# endif
|
# endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class file_buffer final : public buffer<char> {
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
|
||||||
|
|
||||||
|
/// A fast buffered output stream for writing from a single thread. Writing from
|
||||||
|
/// multiple threads without external synchronization may result in a data race.
|
||||||
|
class ostream : private detail::buffer<char> {
|
||||||
private:
|
private:
|
||||||
file file_;
|
file file_;
|
||||||
|
|
||||||
|
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
|
||||||
|
|
||||||
FMT_API static void grow(buffer<char>& buf, size_t);
|
FMT_API static void grow(buffer<char>& buf, size_t);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FMT_API file_buffer(cstring_view path, const ostream_params& params);
|
FMT_API ostream(ostream&& other) noexcept;
|
||||||
FMT_API file_buffer(file_buffer&& other) noexcept;
|
FMT_API ~ostream();
|
||||||
FMT_API ~file_buffer();
|
|
||||||
|
|
||||||
void flush() {
|
operator writer() {
|
||||||
|
detail::buffer<char>& buf = *this;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void flush() {
|
||||||
if (size() == 0) return;
|
if (size() == 0) return;
|
||||||
file_.write(data(), size() * sizeof(data()[0]));
|
file_.write(data(), size() * sizeof(data()[0]));
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
|
||||||
flush();
|
|
||||||
file_.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
constexpr auto buffer_size = detail::buffer_size();
|
|
||||||
|
|
||||||
/// A fast output stream for writing from a single thread. Writing from
|
|
||||||
/// multiple threads without external synchronization may result in a data race.
|
|
||||||
class FMT_API ostream {
|
|
||||||
private:
|
|
||||||
FMT_MSC_WARNING(suppress : 4251)
|
|
||||||
detail::file_buffer buffer_;
|
|
||||||
|
|
||||||
ostream(cstring_view path, const detail::ostream_params& params)
|
|
||||||
: buffer_(path, params) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
|
|
||||||
|
|
||||||
~ostream();
|
|
||||||
|
|
||||||
void flush() { buffer_.flush(); }
|
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
friend auto output_file(cstring_view path, T... params) -> ostream;
|
friend auto output_file(cstring_view path, T... params) -> ostream;
|
||||||
|
|
||||||
void close() { buffer_.close(); }
|
inline void close() {
|
||||||
|
flush();
|
||||||
|
file_.close();
|
||||||
|
}
|
||||||
|
|
||||||
/// Formats `args` according to specifications in `fmt` and writes the
|
/// Formats `args` according to specifications in `fmt` and writes the
|
||||||
/// output to the file.
|
/// output to the file.
|
||||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||||
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...));
|
vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -22,66 +22,39 @@
|
|||||||
|
|
||||||
#include "chrono.h" // formatbuf
|
#include "chrono.h" // formatbuf
|
||||||
|
|
||||||
|
#ifdef _MSVC_STL_UPDATE
|
||||||
|
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
|
||||||
|
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
|
||||||
|
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
|
||||||
|
#else
|
||||||
|
# define FMT_MSVC_STL_UPDATE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// Generate a unique explicit instantion in every translation unit using a tag
|
// Generate a unique explicit instantiation in every translation unit using a
|
||||||
// type in an anonymous namespace.
|
// tag type in an anonymous namespace.
|
||||||
namespace {
|
namespace {
|
||||||
struct file_access_tag {};
|
struct file_access_tag {};
|
||||||
} // namespace
|
} // namespace
|
||||||
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
|
template <typename Tag, typename BufType, FILE* BufType::* FileMemberPtr>
|
||||||
class file_access {
|
class file_access {
|
||||||
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#if FMT_MSC_VERSION
|
#if FMT_MSVC_STL_UPDATE
|
||||||
template class file_access<file_access_tag, std::filebuf,
|
template class file_access<file_access_tag, std::filebuf,
|
||||||
&std::filebuf::_Myfile>;
|
&std::filebuf::_Myfile>;
|
||||||
auto get_file(std::filebuf&) -> FILE*;
|
auto get_file(std::filebuf&) -> FILE*;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
|
|
||||||
-> bool {
|
|
||||||
FILE* f = nullptr;
|
|
||||||
#if FMT_MSC_VERSION && FMT_USE_RTTI
|
|
||||||
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
|
||||||
f = get_file(*buf);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
|
|
||||||
auto* rdbuf = os.rdbuf();
|
|
||||||
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
|
||||||
f = sfbuf->file();
|
|
||||||
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
|
||||||
f = fbuf->file();
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
ignore_unused(os, data, f);
|
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (f) {
|
|
||||||
int fd = _fileno(f);
|
|
||||||
if (_isatty(fd)) {
|
|
||||||
os.flush();
|
|
||||||
return write_console(fd, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inline auto write_ostream_unicode(std::wostream&,
|
|
||||||
fmt::basic_string_view<wchar_t>) -> bool {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the content of buf to os.
|
// Write the content of buf to os.
|
||||||
// It is a separate function rather than a part of vprint to simplify testing.
|
// It is a separate function rather than a part of vprint to simplify testing.
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||||
const Char* buf_data = buf.data();
|
const Char* buf_data = buf.data();
|
||||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
using unsigned_streamsize = make_unsigned_t<std::streamsize>;
|
||||||
unsigned_streamsize size = buf.size();
|
unsigned_streamsize size = buf.size();
|
||||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||||
do {
|
do {
|
||||||
@ -92,21 +65,9 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
|||||||
} while (size != 0);
|
} while (size != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename T>
|
|
||||||
void format_value(buffer<Char>& buf, const T& value) {
|
|
||||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
|
||||||
auto&& output = std::basic_ostream<Char>(&format_buf);
|
|
||||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
|
||||||
output.imbue(std::locale::classic()); // The default is always unlocalized.
|
|
||||||
#endif
|
|
||||||
output << value;
|
|
||||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> struct streamed_view {
|
template <typename T> struct streamed_view {
|
||||||
const T& value;
|
const T& value;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||||
@ -117,7 +78,11 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
|||||||
template <typename T, typename Context>
|
template <typename T, typename Context>
|
||||||
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
|
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
|
||||||
auto buffer = basic_memory_buffer<Char>();
|
auto buffer = basic_memory_buffer<Char>();
|
||||||
detail::format_value(buffer, value);
|
auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
|
||||||
|
auto&& output = std::basic_ostream<Char>(&formatbuf);
|
||||||
|
output.imbue(std::locale::classic()); // The default is always unlocalized.
|
||||||
|
output << value;
|
||||||
|
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||||
return formatter<basic_string_view<Char>, Char>::format(
|
return formatter<basic_string_view<Char>, Char>::format(
|
||||||
{buffer.data(), buffer.size()}, ctx);
|
{buffer.data(), buffer.size()}, ctx);
|
||||||
}
|
}
|
||||||
@ -148,24 +113,30 @@ constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
|
|||||||
return {value};
|
return {value};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
inline void vprint(std::ostream& os, string_view fmt, format_args args) {
|
||||||
|
|
||||||
inline void vprint_directly(std::ostream& os, string_view format_str,
|
|
||||||
format_args args) {
|
|
||||||
auto buffer = memory_buffer();
|
auto buffer = memory_buffer();
|
||||||
detail::vformat_to(buffer, format_str, args);
|
detail::vformat_to(buffer, fmt, args);
|
||||||
detail::write_buffer(os, buffer);
|
FILE* f = nullptr;
|
||||||
}
|
#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
|
||||||
|
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
||||||
} // namespace detail
|
f = detail::get_file(*buf);
|
||||||
|
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
|
||||||
FMT_EXPORT template <typename Char>
|
auto* rdbuf = os.rdbuf();
|
||||||
void vprint(std::basic_ostream<Char>& os,
|
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||||
basic_string_view<type_identity_t<Char>> format_str,
|
f = sfbuf->file();
|
||||||
typename detail::vformat_args<Char>::type args) {
|
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||||
auto buffer = basic_memory_buffer<Char>();
|
f = fbuf->file();
|
||||||
detail::vformat_to(buffer, format_str, args);
|
#endif
|
||||||
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
|
#ifdef _WIN32
|
||||||
|
if (f) {
|
||||||
|
int fd = _fileno(f);
|
||||||
|
if (_isatty(fd)) {
|
||||||
|
os.flush();
|
||||||
|
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
detail::ignore_unused(f);
|
||||||
detail::write_buffer(os, buffer);
|
detail::write_buffer(os, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,32 +149,17 @@ void vprint(std::basic_ostream<Char>& os,
|
|||||||
*/
|
*/
|
||||||
FMT_EXPORT template <typename... T>
|
FMT_EXPORT template <typename... T>
|
||||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||||
const auto& vargs = fmt::make_format_args(args...);
|
fmt::vargs<T...> vargs = {{args...}};
|
||||||
if (detail::use_utf8())
|
if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
|
||||||
vprint(os, fmt, vargs);
|
auto buffer = memory_buffer();
|
||||||
else
|
detail::vformat_to(buffer, fmt.str, vargs);
|
||||||
detail::vprint_directly(os, fmt, vargs);
|
detail::write_buffer(os, buffer);
|
||||||
}
|
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename... Args>
|
|
||||||
void print(std::wostream& os,
|
|
||||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
|
||||||
Args&&... args) {
|
|
||||||
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_EXPORT template <typename... T>
|
FMT_EXPORT template <typename... T>
|
||||||
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||||
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
fmt::print(os, FMT_STRING("{}\n"),
|
||||||
}
|
fmt::format(fmt, std::forward<T>(args)...));
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename... Args>
|
|
||||||
void println(std::wostream& os,
|
|
||||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
|
||||||
Args&&... args) {
|
|
||||||
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
#define FMT_PRINTF_H_
|
#define FMT_PRINTF_H_
|
||||||
|
|
||||||
#ifndef FMT_MODULE
|
#ifndef FMT_MODULE
|
||||||
# include <algorithm> // std::max
|
# include <algorithm> // std::find
|
||||||
# include <limits> // std::numeric_limits
|
# include <limits> // std::numeric_limits
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -18,10 +18,6 @@
|
|||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
FMT_BEGIN_EXPORT
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
template <typename T> struct printf_formatter {
|
|
||||||
printf_formatter() = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char> class basic_printf_context {
|
template <typename Char> class basic_printf_context {
|
||||||
private:
|
private:
|
||||||
basic_appender<Char> out_;
|
basic_appender<Char> out_;
|
||||||
@ -33,8 +29,7 @@ template <typename Char> class basic_printf_context {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
using parse_context_type = basic_format_parse_context<Char>;
|
enum { builtin_types = 1 };
|
||||||
template <typename T> using formatter_type = printf_formatter<T>;
|
|
||||||
|
|
||||||
/// Constructs a `printf_context` object. References to the arguments are
|
/// Constructs a `printf_context` object. References to the arguments are
|
||||||
/// stored in the context object so make sure they have appropriate lifetimes.
|
/// stored in the context object so make sure they have appropriate lifetimes.
|
||||||
@ -45,7 +40,7 @@ template <typename Char> class basic_printf_context {
|
|||||||
auto out() -> basic_appender<Char> { return out_; }
|
auto out() -> basic_appender<Char> { return out_; }
|
||||||
void advance_to(basic_appender<Char>) {}
|
void advance_to(basic_appender<Char>) {}
|
||||||
|
|
||||||
auto locale() -> detail::locale_ref { return {}; }
|
auto locale() -> locale_ref { return {}; }
|
||||||
|
|
||||||
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
||||||
return args_.get(id);
|
return args_.get(id);
|
||||||
@ -54,14 +49,30 @@ template <typename Char> class basic_printf_context {
|
|||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
|
// Return the result via the out param to workaround gcc bug 77539.
|
||||||
|
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
|
||||||
|
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
|
||||||
|
for (out = first; out != last; ++out) {
|
||||||
|
if (*out == value) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline auto find<false, char>(const char* first, const char* last, char value,
|
||||||
|
const char*& out) -> bool {
|
||||||
|
out =
|
||||||
|
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
|
||||||
|
return out != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||||
// signed and unsigned integers.
|
// signed and unsigned integers.
|
||||||
template <bool IsSigned> struct int_checker {
|
template <bool IS_SIGNED> struct int_checker {
|
||||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||||
unsigned max = to_unsigned(max_value<int>());
|
return value <= to_unsigned(max_value<int>());
|
||||||
return value <= max;
|
|
||||||
}
|
}
|
||||||
static auto fits_in_int(bool) -> bool { return true; }
|
inline static auto fits_in_int(bool) -> bool { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct int_checker<true> {
|
template <> struct int_checker<true> {
|
||||||
@ -69,7 +80,7 @@ template <> struct int_checker<true> {
|
|||||||
return value >= (std::numeric_limits<int>::min)() &&
|
return value >= (std::numeric_limits<int>::min)() &&
|
||||||
value <= max_value<int>();
|
value <= max_value<int>();
|
||||||
}
|
}
|
||||||
static auto fits_in_int(int) -> bool { return true; }
|
inline static auto fits_in_int(int) -> bool { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct printf_precision_handler {
|
struct printf_precision_handler {
|
||||||
@ -77,7 +88,7 @@ struct printf_precision_handler {
|
|||||||
auto operator()(T value) -> int {
|
auto operator()(T value) -> int {
|
||||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||||
report_error("number is too big");
|
report_error("number is too big");
|
||||||
return (std::max)(static_cast<int>(value), 0);
|
return max_of(static_cast<int>(value), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
@ -127,25 +138,19 @@ template <typename T, typename Context> class arg_converter {
|
|||||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||||
// Extra casts are used to silence warnings.
|
// Extra casts are used to silence warnings.
|
||||||
if (is_signed) {
|
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||||
auto n = static_cast<int>(static_cast<target_type>(value));
|
if (is_signed)
|
||||||
arg_ = detail::make_arg<Context>(n);
|
arg_ = static_cast<int>(static_cast<target_type>(value));
|
||||||
} else {
|
else
|
||||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
||||||
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
|
||||||
arg_ = detail::make_arg<Context>(n);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (is_signed) {
|
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
// std::printf("%lld", -42); // prints "4294967254"
|
||||||
// std::printf("%lld", -42); // prints "4294967254"
|
// but we don't have to do the same because it's a UB.
|
||||||
// but we don't have to do the same because it's a UB.
|
if (is_signed)
|
||||||
auto n = static_cast<long long>(value);
|
arg_ = static_cast<long long>(value);
|
||||||
arg_ = detail::make_arg<Context>(n);
|
else
|
||||||
} else {
|
arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
||||||
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
|
||||||
arg_ = detail::make_arg<Context>(n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,8 +177,7 @@ template <typename Context> class char_converter {
|
|||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
void operator()(T value) {
|
void operator()(T value) {
|
||||||
auto c = static_cast<typename Context::char_type>(value);
|
arg_ = static_cast<typename Context::char_type>(value);
|
||||||
arg_ = detail::make_arg<Context>(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
@ -194,13 +198,13 @@ class printf_width_handler {
|
|||||||
format_specs& specs_;
|
format_specs& specs_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
auto operator()(T value) -> unsigned {
|
auto operator()(T value) -> unsigned {
|
||||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||||
if (detail::is_negative(value)) {
|
if (detail::is_negative(value)) {
|
||||||
specs_.align = align::left;
|
specs_.set_align(align::left);
|
||||||
width = 0 - width;
|
width = 0 - width;
|
||||||
}
|
}
|
||||||
unsigned int_max = to_unsigned(max_value<int>());
|
unsigned int_max = to_unsigned(max_value<int>());
|
||||||
@ -234,69 +238,74 @@ class printf_arg_formatter : public arg_formatter<Char> {
|
|||||||
|
|
||||||
void write_null_pointer(bool is_string = false) {
|
void write_null_pointer(bool is_string = false) {
|
||||||
auto s = this->specs;
|
auto s = this->specs;
|
||||||
s.type = presentation_type::none;
|
s.set_type(presentation_type::none);
|
||||||
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
|
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> void write(T value) {
|
||||||
|
detail::write<Char>(this->out, value, this->specs, this->locale);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
|
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
|
||||||
context_type& ctx)
|
context_type& ctx)
|
||||||
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
||||||
|
|
||||||
void operator()(monostate value) { base::operator()(value); }
|
void operator()(monostate value) { write(value); }
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||||
void operator()(T value) {
|
void operator()(T value) {
|
||||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||||
// std::is_same instead.
|
// std::is_same instead.
|
||||||
if (!std::is_same<T, Char>::value) {
|
if (!std::is_same<T, Char>::value) {
|
||||||
base::operator()(value);
|
write(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
format_specs s = this->specs;
|
format_specs s = this->specs;
|
||||||
if (s.type != presentation_type::none && s.type != presentation_type::chr) {
|
if (s.type() != presentation_type::none &&
|
||||||
|
s.type() != presentation_type::chr) {
|
||||||
return (*this)(static_cast<int>(value));
|
return (*this)(static_cast<int>(value));
|
||||||
}
|
}
|
||||||
s.sign = sign::none;
|
s.set_sign(sign::none);
|
||||||
s.alt = false;
|
s.clear_alt();
|
||||||
s.fill = ' '; // Ignore '0' flag for char types.
|
s.set_fill(' '); // Ignore '0' flag for char types.
|
||||||
// align::numeric needs to be overwritten here since the '0' flag is
|
// align::numeric needs to be overwritten here since the '0' flag is
|
||||||
// ignored for non-numeric types
|
// ignored for non-numeric types
|
||||||
if (s.align == align::none || s.align == align::numeric)
|
if (s.align() == align::none || s.align() == align::numeric)
|
||||||
s.align = align::right;
|
s.set_align(align::right);
|
||||||
write<Char>(this->out, static_cast<Char>(value), s);
|
detail::write<Char>(this->out, static_cast<Char>(value), s);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||||
void operator()(T value) {
|
void operator()(T value) {
|
||||||
base::operator()(value);
|
write(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const char* value) {
|
void operator()(const char* value) {
|
||||||
if (value)
|
if (value)
|
||||||
base::operator()(value);
|
write(value);
|
||||||
else
|
else
|
||||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
write_null_pointer(this->specs.type() != presentation_type::pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const wchar_t* value) {
|
void operator()(const wchar_t* value) {
|
||||||
if (value)
|
if (value)
|
||||||
base::operator()(value);
|
write(value);
|
||||||
else
|
else
|
||||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
write_null_pointer(this->specs.type() != presentation_type::pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(basic_string_view<Char> value) { base::operator()(value); }
|
void operator()(basic_string_view<Char> value) { write(value); }
|
||||||
|
|
||||||
void operator()(const void* value) {
|
void operator()(const void* value) {
|
||||||
if (value)
|
if (value)
|
||||||
base::operator()(value);
|
write(value);
|
||||||
else
|
else
|
||||||
write_null_pointer();
|
write_null_pointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||||
auto parse_ctx = basic_format_parse_context<Char>({});
|
auto parse_ctx = parse_context<Char>({});
|
||||||
handle.format(parse_ctx, context_);
|
handle.format(parse_ctx, context_);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -305,23 +314,14 @@ template <typename Char>
|
|||||||
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
|
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
switch (*it) {
|
switch (*it) {
|
||||||
case '-':
|
case '-': specs.set_align(align::left); break;
|
||||||
specs.align = align::left;
|
case '+': specs.set_sign(sign::plus); break;
|
||||||
break;
|
case '0': specs.set_fill('0'); break;
|
||||||
case '+':
|
|
||||||
specs.sign = sign::plus;
|
|
||||||
break;
|
|
||||||
case '0':
|
|
||||||
specs.fill = '0';
|
|
||||||
break;
|
|
||||||
case ' ':
|
case ' ':
|
||||||
if (specs.sign != sign::plus) specs.sign = sign::space;
|
if (specs.sign() != sign::plus) specs.set_sign(sign::space);
|
||||||
break;
|
break;
|
||||||
case '#':
|
case '#': specs.set_alt(); break;
|
||||||
specs.alt = true;
|
default: return;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,7 +339,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs,
|
|||||||
++it;
|
++it;
|
||||||
arg_index = value != -1 ? value : max_value<int>();
|
arg_index = value != -1 ? value : max_value<int>();
|
||||||
} else {
|
} else {
|
||||||
if (c == '0') specs.fill = '0';
|
if (c == '0') specs.set_fill('0');
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
// Nonzero value means that we parsed width and don't need to
|
// Nonzero value means that we parsed width and don't need to
|
||||||
// parse it or flags again, so return now.
|
// parse it or flags again, so return now.
|
||||||
@ -369,43 +369,22 @@ inline auto parse_printf_presentation_type(char c, type t, bool& upper)
|
|||||||
using pt = presentation_type;
|
using pt = presentation_type;
|
||||||
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
|
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'd':
|
case 'd': return in(t, integral_set) ? pt::dec : pt::none;
|
||||||
return in(t, integral_set) ? pt::dec : pt::none;
|
case 'o': return in(t, integral_set) ? pt::oct : pt::none;
|
||||||
case 'o':
|
case 'X': upper = true; FMT_FALLTHROUGH;
|
||||||
return in(t, integral_set) ? pt::oct : pt::none;
|
case 'x': return in(t, integral_set) ? pt::hex : pt::none;
|
||||||
case 'X':
|
case 'E': upper = true; FMT_FALLTHROUGH;
|
||||||
upper = true;
|
case 'e': return in(t, float_set) ? pt::exp : pt::none;
|
||||||
FMT_FALLTHROUGH;
|
case 'F': upper = true; FMT_FALLTHROUGH;
|
||||||
case 'x':
|
case 'f': return in(t, float_set) ? pt::fixed : pt::none;
|
||||||
return in(t, integral_set) ? pt::hex : pt::none;
|
case 'G': upper = true; FMT_FALLTHROUGH;
|
||||||
case 'E':
|
case 'g': return in(t, float_set) ? pt::general : pt::none;
|
||||||
upper = true;
|
case 'A': upper = true; FMT_FALLTHROUGH;
|
||||||
FMT_FALLTHROUGH;
|
case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
|
||||||
case 'e':
|
case 'c': return in(t, integral_set) ? pt::chr : pt::none;
|
||||||
return in(t, float_set) ? pt::exp : pt::none;
|
case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
|
||||||
case 'F':
|
case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
|
||||||
upper = true;
|
default: return pt::none;
|
||||||
FMT_FALLTHROUGH;
|
|
||||||
case 'f':
|
|
||||||
return in(t, float_set) ? pt::fixed : pt::none;
|
|
||||||
case 'G':
|
|
||||||
upper = true;
|
|
||||||
FMT_FALLTHROUGH;
|
|
||||||
case 'g':
|
|
||||||
return in(t, float_set) ? pt::general : pt::none;
|
|
||||||
case 'A':
|
|
||||||
upper = true;
|
|
||||||
FMT_FALLTHROUGH;
|
|
||||||
case 'a':
|
|
||||||
return in(t, float_set) ? pt::hexfloat : pt::none;
|
|
||||||
case 'c':
|
|
||||||
return in(t, integral_set) ? pt::chr : pt::none;
|
|
||||||
case 's':
|
|
||||||
return in(t, string_set | cstring_set) ? pt::string : pt::none;
|
|
||||||
case 'p':
|
|
||||||
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
|
|
||||||
default:
|
|
||||||
return pt::none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,7 +394,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||||||
using iterator = basic_appender<Char>;
|
using iterator = basic_appender<Char>;
|
||||||
auto out = iterator(buf);
|
auto out = iterator(buf);
|
||||||
auto context = basic_printf_context<Char>(out, args);
|
auto context = basic_printf_context<Char>(out, args);
|
||||||
auto parse_ctx = basic_format_parse_context<Char>(format);
|
auto parse_ctx = parse_context<Char>(format);
|
||||||
|
|
||||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||||
// argument.
|
// argument.
|
||||||
@ -424,7 +403,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||||||
arg_index = parse_ctx.next_arg_id();
|
arg_index = parse_ctx.next_arg_id();
|
||||||
else
|
else
|
||||||
parse_ctx.check_arg_id(--arg_index);
|
parse_ctx.check_arg_id(--arg_index);
|
||||||
return detail::get_arg(context, arg_index);
|
auto arg = context.arg(arg_index);
|
||||||
|
if (!arg) report_error("argument not found");
|
||||||
|
return arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Char* start = parse_ctx.begin();
|
const Char* start = parse_ctx.begin();
|
||||||
@ -444,7 +425,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||||||
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||||
|
|
||||||
auto specs = format_specs();
|
auto specs = format_specs();
|
||||||
specs.align = align::right;
|
specs.set_align(align::right);
|
||||||
|
|
||||||
// Parse argument index, flags and width.
|
// Parse argument index, flags and width.
|
||||||
int arg_index = parse_header(it, end, specs, get_arg);
|
int arg_index = parse_header(it, end, specs, get_arg);
|
||||||
@ -468,9 +449,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||||||
auto arg = get_arg(arg_index);
|
auto arg = get_arg(arg_index);
|
||||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||||
// specified, the '0' flag is ignored
|
// specified, the '0' flag is ignored
|
||||||
if (specs.precision >= 0 && arg.is_integral()) {
|
if (specs.precision >= 0 && is_integral_type(arg.type())) {
|
||||||
// Ignore '0' for non-numeric types or if '-' present.
|
// Ignore '0' for non-numeric types or if '-' present.
|
||||||
specs.fill = ' ';
|
specs.set_fill(' ');
|
||||||
}
|
}
|
||||||
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
|
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
|
||||||
auto str = arg.visit(get_cstring<Char>());
|
auto str = arg.visit(get_cstring<Char>());
|
||||||
@ -478,15 +459,16 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||||||
auto nul = std::find(str, str_end, Char());
|
auto nul = std::find(str, str_end, Char());
|
||||||
auto sv = basic_string_view<Char>(
|
auto sv = basic_string_view<Char>(
|
||||||
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
||||||
arg = make_arg<basic_printf_context<Char>>(sv);
|
arg = sv;
|
||||||
}
|
}
|
||||||
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false;
|
if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
|
||||||
if (specs.fill.template get<Char>() == '0') {
|
if (specs.fill_unit<Char>() == '0') {
|
||||||
if (arg.is_arithmetic() && specs.align != align::left)
|
if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
|
||||||
specs.align = align::numeric;
|
specs.set_align(align::numeric);
|
||||||
else
|
} else {
|
||||||
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
// Ignore '0' flag for non-numeric types or if '-' flag is also present.
|
||||||
// flag is also present.
|
specs.set_fill(' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse length and convert the argument to the required type.
|
// Parse length and convert the argument to the required type.
|
||||||
@ -511,44 +493,34 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|||||||
convert_arg<long>(arg, t);
|
convert_arg<long>(arg, t);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'j':
|
case 'j': convert_arg<intmax_t>(arg, t); break;
|
||||||
convert_arg<intmax_t>(arg, t);
|
case 'z': convert_arg<size_t>(arg, t); break;
|
||||||
break;
|
case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
|
||||||
case 'z':
|
|
||||||
convert_arg<size_t>(arg, t);
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
convert_arg<std::ptrdiff_t>(arg, t);
|
|
||||||
break;
|
|
||||||
case 'L':
|
case 'L':
|
||||||
// printf produces garbage when 'L' is omitted for long double, no
|
// printf produces garbage when 'L' is omitted for long double, no
|
||||||
// need to do the same.
|
// need to do the same.
|
||||||
break;
|
break;
|
||||||
default:
|
default: --it; convert_arg<void>(arg, c);
|
||||||
--it;
|
|
||||||
convert_arg<void>(arg, c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse type.
|
// Parse type.
|
||||||
if (it == end) report_error("invalid format string");
|
if (it == end) report_error("invalid format string");
|
||||||
char type = static_cast<char>(*it++);
|
char type = static_cast<char>(*it++);
|
||||||
if (arg.is_integral()) {
|
if (is_integral_type(arg.type())) {
|
||||||
// Normalize type.
|
// Normalize type.
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'i':
|
case 'i':
|
||||||
case 'u':
|
case 'u': type = 'd'; break;
|
||||||
type = 'd';
|
|
||||||
break;
|
|
||||||
case 'c':
|
case 'c':
|
||||||
arg.visit(char_converter<basic_printf_context<Char>>(arg));
|
arg.visit(char_converter<basic_printf_context<Char>>(arg));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool upper = false;
|
bool upper = false;
|
||||||
specs.type = parse_printf_presentation_type(type, arg.type(), upper);
|
specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
|
||||||
if (specs.type == presentation_type::none)
|
if (specs.type() == presentation_type::none)
|
||||||
report_error("invalid format specifier");
|
report_error("invalid format specifier");
|
||||||
specs.upper = upper;
|
if (upper) specs.set_upper();
|
||||||
|
|
||||||
start = it;
|
start = it;
|
||||||
|
|
||||||
@ -583,7 +555,7 @@ inline auto vsprintf(basic_string_view<Char> fmt,
|
|||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
auto buf = basic_memory_buffer<Char>();
|
auto buf = basic_memory_buffer<Char>();
|
||||||
detail::vprintf(buf, fmt, args);
|
detail::vprintf(buf, fmt, args);
|
||||||
return to_string(buf);
|
return {buf.data(), buf.size()};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -594,15 +566,19 @@ inline auto vsprintf(basic_string_view<Char> fmt,
|
|||||||
*
|
*
|
||||||
* std::string message = fmt::sprintf("The answer is %d", 42);
|
* std::string message = fmt::sprintf("The answer is %d", 42);
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... T, typename Char = char_t<S>>
|
template <typename... T>
|
||||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
inline auto sprintf(string_view fmt, const T&... args) -> std::string {
|
||||||
return vsprintf(detail::to_string_view(fmt),
|
return vsprintf(fmt, make_printf_args(args...));
|
||||||
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
}
|
||||||
|
template <typename... T>
|
||||||
|
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
|
||||||
|
-> std::wstring {
|
||||||
|
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
|
auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
|
||||||
typename vprintf_args<Char>::type args) -> int {
|
typename vprintf_args<Char>::type args) -> int {
|
||||||
auto buf = basic_memory_buffer<Char>();
|
auto buf = basic_memory_buffer<Char>();
|
||||||
detail::vprintf(buf, fmt, args);
|
detail::vprintf(buf, fmt, args);
|
||||||
size_t size = buf.size();
|
size_t size = buf.size();
|
||||||
@ -619,17 +595,14 @@ inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
|
|||||||
*
|
*
|
||||||
* fmt::fprintf(stderr, "Don't %s!", "panic");
|
* fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... T, typename Char = char_t<S>>
|
template <typename... T>
|
||||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
|
||||||
return vfprintf(f, detail::to_string_view(fmt),
|
return vfprintf(f, fmt, make_printf_args(args...));
|
||||||
make_printf_args<Char>(args...));
|
|
||||||
}
|
}
|
||||||
|
template <typename... T>
|
||||||
template <typename Char>
|
FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
|
||||||
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,
|
const T&... args) -> int {
|
||||||
typename vprintf_args<Char>::type args)
|
return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
|
||||||
-> int {
|
|
||||||
return vfprintf(stdout, fmt, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -644,11 +617,6 @@ template <typename... T>
|
|||||||
inline auto printf(string_view fmt, const T&... args) -> int {
|
inline auto printf(string_view fmt, const T&... args) -> int {
|
||||||
return vfprintf(stdout, fmt, make_printf_args(args...));
|
return vfprintf(stdout, fmt, make_printf_args(args...));
|
||||||
}
|
}
|
||||||
template <typename... T>
|
|
||||||
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
|
|
||||||
const T&... args) -> int {
|
|
||||||
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_END_EXPORT
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
#ifndef FMT_MODULE
|
#ifndef FMT_MODULE
|
||||||
# include <initializer_list>
|
# include <initializer_list>
|
||||||
# include <iterator>
|
# include <iterator>
|
||||||
# include <string>
|
|
||||||
# include <tuple>
|
# include <tuple>
|
||||||
# include <type_traits>
|
# include <type_traits>
|
||||||
# include <utility>
|
# include <utility>
|
||||||
@ -19,6 +18,13 @@
|
|||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
|
#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
|
||||||
|
# define FMT_LIFETIMEBOUND [[clang::lifetimebound]]
|
||||||
|
#else
|
||||||
|
# define FMT_LIFETIMEBOUND
|
||||||
|
#endif
|
||||||
|
FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address")
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
FMT_EXPORT
|
FMT_EXPORT
|
||||||
@ -31,7 +37,7 @@ template <typename T> class is_map {
|
|||||||
template <typename> static void check(...);
|
template <typename> static void check(...);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value =
|
static constexpr bool value =
|
||||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,29 +46,16 @@ template <typename T> class is_set {
|
|||||||
template <typename> static void check(...);
|
template <typename> static void check(...);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value =
|
static constexpr bool value =
|
||||||
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Ts> struct conditional_helper {};
|
|
||||||
|
|
||||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
|
||||||
|
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
|
|
||||||
|
|
||||||
# define FMT_DECLTYPE_RETURN(val) \
|
|
||||||
->decltype(val) { return val; } \
|
|
||||||
static_assert( \
|
|
||||||
true, "") // This makes it so that a semicolon is required after the
|
|
||||||
// macro, which helps clang-format handle the formatting.
|
|
||||||
|
|
||||||
// C array overload
|
// C array overload
|
||||||
template <typename T, std::size_t N>
|
template <typename T, size_t N>
|
||||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
template <typename T, std::size_t N>
|
template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
|
||||||
auto range_end(const T (&arr)[N]) -> const T* {
|
|
||||||
return arr + N;
|
return arr + N;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,9 +69,13 @@ struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
|
|||||||
|
|
||||||
// Member function overloads.
|
// Member function overloads.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
auto range_begin(T&& rng) -> decltype(static_cast<T&&>(rng).begin()) {
|
||||||
|
return static_cast<T&&>(rng).begin();
|
||||||
|
}
|
||||||
template <typename T>
|
template <typename T>
|
||||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
auto range_end(T&& rng) -> decltype(static_cast<T&&>(rng).end()) {
|
||||||
|
return static_cast<T&&>(rng).end();
|
||||||
|
}
|
||||||
|
|
||||||
// ADL overloads. Only participate in overload resolution if member functions
|
// ADL overloads. Only participate in overload resolution if member functions
|
||||||
// are not found.
|
// are not found.
|
||||||
@ -115,21 +112,20 @@ struct has_mutable_begin_end<
|
|||||||
// SFINAE properly unless there are distinct types
|
// SFINAE properly unless there are distinct types
|
||||||
int>> : std::true_type {};
|
int>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_range_<T, void>
|
struct is_range_<T, void>
|
||||||
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||||
has_mutable_begin_end<T>::value)> {};
|
has_mutable_begin_end<T>::value)> {};
|
||||||
# undef FMT_DECLTYPE_RETURN
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// tuple_size and tuple_element check.
|
// tuple_size and tuple_element check.
|
||||||
template <typename T> class is_tuple_like_ {
|
template <typename T> class is_tuple_like_ {
|
||||||
template <typename U>
|
template <typename U, typename V = typename std::remove_cv<U>::type>
|
||||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);
|
||||||
template <typename> static void check(...);
|
template <typename> static void check(...);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value =
|
static constexpr bool value =
|
||||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,7 +159,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
|
|||||||
template <typename T, typename C, bool = is_tuple_like_<T>::value>
|
template <typename T, typename C, bool = is_tuple_like_<T>::value>
|
||||||
class is_tuple_formattable_ {
|
class is_tuple_formattable_ {
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value = false;
|
static constexpr bool value = false;
|
||||||
};
|
};
|
||||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||||
template <size_t... Is>
|
template <size_t... Is>
|
||||||
@ -179,7 +175,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
|||||||
C>::value)...>{}));
|
C>::value)...>{}));
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value =
|
static constexpr bool value =
|
||||||
decltype(check(tuple_index_sequence<T>{}))::value;
|
decltype(check(tuple_index_sequence<T>{}))::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -217,7 +213,7 @@ template <typename Char, typename... T>
|
|||||||
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
|
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
|
||||||
|
|
||||||
using std::get;
|
using std::get;
|
||||||
template <typename Tuple, typename Char, std::size_t... Is>
|
template <typename Tuple, typename Char, size_t... Is>
|
||||||
auto get_formatters(index_sequence<Is...>)
|
auto get_formatters(index_sequence<Is...>)
|
||||||
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
|
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
|
||||||
} // namespace tuple
|
} // namespace tuple
|
||||||
@ -228,7 +224,7 @@ template <typename R> struct range_reference_type_impl {
|
|||||||
using type = decltype(*detail::range_begin(std::declval<R&>()));
|
using type = decltype(*detail::range_begin(std::declval<R&>()));
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
|
template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
|
||||||
using type = T&;
|
using type = T&;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -245,14 +241,6 @@ using range_reference_type =
|
|||||||
template <typename Range>
|
template <typename Range>
|
||||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||||
|
|
||||||
template <typename Formatter>
|
|
||||||
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
|
|
||||||
-> decltype(f.set_debug_format(set)) {
|
|
||||||
f.set_debug_format(set);
|
|
||||||
}
|
|
||||||
template <typename Formatter>
|
|
||||||
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct range_format_kind_
|
struct range_format_kind_
|
||||||
: std::integral_constant<range_format,
|
: std::integral_constant<range_format,
|
||||||
@ -266,12 +254,12 @@ template <range_format K>
|
|||||||
using range_format_constant = std::integral_constant<range_format, K>;
|
using range_format_constant = std::integral_constant<range_format, K>;
|
||||||
|
|
||||||
// These are not generic lambdas for compatibility with C++11.
|
// These are not generic lambdas for compatibility with C++11.
|
||||||
template <typename ParseContext> struct parse_empty_specs {
|
template <typename Char> struct parse_empty_specs {
|
||||||
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
|
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
|
||||||
f.parse(ctx);
|
f.parse(ctx);
|
||||||
detail::maybe_set_debug_format(f, true);
|
detail::maybe_set_debug_format(f, true);
|
||||||
}
|
}
|
||||||
ParseContext& ctx;
|
parse_context<Char>& ctx;
|
||||||
};
|
};
|
||||||
template <typename FormatContext> struct format_tuple_element {
|
template <typename FormatContext> struct format_tuple_element {
|
||||||
using char_type = typename FormatContext::char_type;
|
using char_type = typename FormatContext::char_type;
|
||||||
@ -290,14 +278,15 @@ template <typename FormatContext> struct format_tuple_element {
|
|||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
template <typename T> struct is_tuple_like {
|
template <typename T> struct is_tuple_like {
|
||||||
static constexpr const bool value =
|
static constexpr bool value =
|
||||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
template <typename T, typename C> struct is_tuple_formattable {
|
template <typename T, typename C> struct is_tuple_formattable {
|
||||||
static constexpr const bool value =
|
static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
|
||||||
detail::is_tuple_formattable_<T, C>::value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Tuple, typename Char>
|
template <typename Tuple, typename Char>
|
||||||
@ -327,11 +316,17 @@ struct formatter<Tuple, Char,
|
|||||||
closing_bracket_ = close;
|
closing_bracket_ = close;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
if (it != ctx.end() && *it != '}') report_error("invalid format specifier");
|
auto end = ctx.end();
|
||||||
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
|
if (it != end && detail::to_ascii(*it) == 'n') {
|
||||||
|
++it;
|
||||||
|
set_brackets({}, {});
|
||||||
|
set_separator({});
|
||||||
|
}
|
||||||
|
if (it != end && *it != '}') report_error("invalid format specifier");
|
||||||
|
ctx.advance_to(it);
|
||||||
|
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,44 +341,24 @@ struct formatter<Tuple, Char,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
template <typename T, typename Char> struct is_range {
|
template <typename T, typename Char> struct is_range {
|
||||||
static constexpr const bool value =
|
static constexpr bool value =
|
||||||
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
|
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Context> struct range_mapper {
|
|
||||||
using mapper = arg_mapper<Context>;
|
|
||||||
|
|
||||||
template <typename T,
|
|
||||||
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
|
|
||||||
static auto map(T&& value) -> T&& {
|
|
||||||
return static_cast<T&&>(value);
|
|
||||||
}
|
|
||||||
template <typename T,
|
|
||||||
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
|
|
||||||
static auto map(T&& value)
|
|
||||||
-> decltype(mapper().map(static_cast<T&&>(value))) {
|
|
||||||
return mapper().map(static_cast<T&&>(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char, typename Element>
|
template <typename Char, typename Element>
|
||||||
using range_formatter_type =
|
using range_formatter_type = formatter<remove_cvref_t<Element>, Char>;
|
||||||
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
|
|
||||||
.map(std::declval<Element>()))>,
|
|
||||||
Char>;
|
|
||||||
|
|
||||||
template <typename R>
|
template <typename R>
|
||||||
using maybe_const_range =
|
using maybe_const_range =
|
||||||
conditional_t<has_const_begin_end<R>::value, const R, R>;
|
conditional_t<has_const_begin_end<R>::value, const R, R>;
|
||||||
|
|
||||||
// Workaround a bug in MSVC 2015 and earlier.
|
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
|
||||||
template <typename R, typename Char>
|
template <typename R, typename Char>
|
||||||
struct is_formattable_delayed
|
struct is_formattable_delayed
|
||||||
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
|
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
|
||||||
#endif
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename...> struct conjunction : std::true_type {};
|
template <typename...> struct conjunction : std::true_type {};
|
||||||
@ -392,6 +367,7 @@ template <typename P1, typename... Pn>
|
|||||||
struct conjunction<P1, Pn...>
|
struct conjunction<P1, Pn...>
|
||||||
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
|
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
template <typename T, typename Char, typename Enable = void>
|
template <typename T, typename Char, typename Enable = void>
|
||||||
struct range_formatter;
|
struct range_formatter;
|
||||||
|
|
||||||
@ -415,7 +391,7 @@ struct range_formatter<
|
|||||||
auto buf = basic_memory_buffer<Char>();
|
auto buf = basic_memory_buffer<Char>();
|
||||||
for (; it != end; ++it) buf.push_back(*it);
|
for (; it != end; ++it) buf.push_back(*it);
|
||||||
auto specs = format_specs();
|
auto specs = format_specs();
|
||||||
specs.type = presentation_type::debug;
|
specs.set_type(presentation_type::debug);
|
||||||
return detail::write<Char>(
|
return detail::write<Char>(
|
||||||
out, basic_string_view<Char>(buf.data(), buf.size()), specs);
|
out, basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||||
}
|
}
|
||||||
@ -443,8 +419,7 @@ struct range_formatter<
|
|||||||
closing_bracket_ = close;
|
closing_bracket_ = close;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
auto end = ctx.end();
|
auto end = ctx.end();
|
||||||
detail::maybe_set_debug_format(underlying_, true);
|
detail::maybe_set_debug_format(underlying_, true);
|
||||||
@ -486,11 +461,10 @@ struct range_formatter<
|
|||||||
|
|
||||||
template <typename R, typename FormatContext>
|
template <typename R, typename FormatContext>
|
||||||
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
|
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
auto mapper = detail::range_mapper<buffered_context<Char>>();
|
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
auto it = detail::range_begin(range);
|
auto it = detail::range_begin(range);
|
||||||
auto end = detail::range_end(range);
|
auto end = detail::range_end(range);
|
||||||
if (is_debug) return write_debug_string(out, it, end);
|
if (is_debug) return write_debug_string(out, std::move(it), end);
|
||||||
|
|
||||||
out = detail::copy<Char>(opening_bracket_, out);
|
out = detail::copy<Char>(opening_bracket_, out);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -498,7 +472,7 @@ struct range_formatter<
|
|||||||
if (i > 0) out = detail::copy<Char>(separator_, out);
|
if (i > 0) out = detail::copy<Char>(separator_, out);
|
||||||
ctx.advance_to(out);
|
ctx.advance_to(out);
|
||||||
auto&& item = *it; // Need an lvalue
|
auto&& item = *it; // Need an lvalue
|
||||||
out = underlying_.format(mapper.map(item), ctx);
|
out = underlying_.format(item, ctx);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
out = detail::copy<Char>(closing_bracket_, out);
|
out = detail::copy<Char>(closing_bracket_, out);
|
||||||
@ -521,13 +495,8 @@ struct formatter<
|
|||||||
range_format_kind<R, Char>::value != range_format::disabled &&
|
range_format_kind<R, Char>::value != range_format::disabled &&
|
||||||
range_format_kind<R, Char>::value != range_format::map &&
|
range_format_kind<R, Char>::value != range_format::map &&
|
||||||
range_format_kind<R, Char>::value != range_format::string &&
|
range_format_kind<R, Char>::value != range_format::string &&
|
||||||
range_format_kind<R, Char>::value != range_format::debug_string>
|
range_format_kind<R, Char>::value != range_format::debug_string>,
|
||||||
// Workaround a bug in MSVC 2015 and earlier.
|
detail::is_formattable_delayed<R, Char>>::value>> {
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
|
||||||
,
|
|
||||||
detail::is_formattable_delayed<R, Char>
|
|
||||||
#endif
|
|
||||||
>::value>> {
|
|
||||||
private:
|
private:
|
||||||
using range_type = detail::maybe_const_range<R>;
|
using range_type = detail::maybe_const_range<R>;
|
||||||
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
|
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
|
||||||
@ -543,8 +512,7 @@ struct formatter<
|
|||||||
detail::string_literal<Char, '}'>{});
|
detail::string_literal<Char, '}'>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return range_formatter_.parse(ctx);
|
return range_formatter_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +527,9 @@ struct formatter<
|
|||||||
template <typename R, typename Char>
|
template <typename R, typename Char>
|
||||||
struct formatter<
|
struct formatter<
|
||||||
R, Char,
|
R, Char,
|
||||||
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
|
enable_if_t<conjunction<
|
||||||
|
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
|
||||||
|
detail::is_formattable_delayed<R, Char>>::value>> {
|
||||||
private:
|
private:
|
||||||
using map_type = detail::maybe_const_range<R>;
|
using map_type = detail::maybe_const_range<R>;
|
||||||
using element_type = detail::uncvref_type<map_type>;
|
using element_type = detail::uncvref_type<map_type>;
|
||||||
@ -571,8 +541,7 @@ struct formatter<
|
|||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR formatter() {}
|
FMT_CONSTEXPR formatter() {}
|
||||||
|
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
auto end = ctx.end();
|
auto end = ctx.end();
|
||||||
if (it != end) {
|
if (it != end) {
|
||||||
@ -586,7 +555,7 @@ struct formatter<
|
|||||||
}
|
}
|
||||||
ctx.advance_to(it);
|
ctx.advance_to(it);
|
||||||
}
|
}
|
||||||
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
|
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,12 +565,11 @@ struct formatter<
|
|||||||
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
|
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
|
||||||
if (!no_delimiters_) out = detail::copy<Char>(open, out);
|
if (!no_delimiters_) out = detail::copy<Char>(open, out);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
auto mapper = detail::range_mapper<buffered_context<Char>>();
|
|
||||||
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
|
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
|
||||||
for (auto&& value : map) {
|
for (auto&& value : map) {
|
||||||
if (i > 0) out = detail::copy<Char>(sep, out);
|
if (i > 0) out = detail::copy<Char>(sep, out);
|
||||||
ctx.advance_to(out);
|
ctx.advance_to(out);
|
||||||
detail::for_each2(formatters_, mapper.map(value),
|
detail::for_each2(formatters_, value,
|
||||||
detail::format_tuple_element<FormatContext>{
|
detail::format_tuple_element<FormatContext>{
|
||||||
0, ctx, detail::string_literal<Char, ':', ' '>{}});
|
0, ctx, detail::string_literal<Char, ':', ' '>{}});
|
||||||
++i;
|
++i;
|
||||||
@ -631,8 +599,7 @@ struct formatter<
|
|||||||
formatter<string_type, Char> underlying_;
|
formatter<string_type, Char> underlying_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return underlying_.parse(ctx);
|
return underlying_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,22 +640,22 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
|
|||||||
#endif
|
#endif
|
||||||
formatter<remove_cvref_t<value_type>, Char> value_formatter_;
|
formatter<remove_cvref_t<value_type>, Char> value_formatter_;
|
||||||
|
|
||||||
using view_ref = conditional_t<std::is_copy_constructible<It>::value,
|
using view = conditional_t<std::is_copy_constructible<It>::value,
|
||||||
const join_view<It, Sentinel, Char>&,
|
const join_view<It, Sentinel, Char>,
|
||||||
join_view<It, Sentinel, Char>&&>;
|
join_view<It, Sentinel, Char>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using nonlocking = void;
|
using nonlocking = void;
|
||||||
|
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
|
|
||||||
return value_formatter_.parse(ctx);
|
return value_formatter_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(view_ref& value, FormatContext& ctx) const
|
auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
-> decltype(ctx.out()) {
|
using iter =
|
||||||
auto it = std::forward<view_ref>(value).begin;
|
conditional_t<std::is_copy_constructible<view>::value, It, It&>;
|
||||||
|
iter it = value.begin;
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
if (it == value.end) return out;
|
if (it == value.end) return out;
|
||||||
out = value_formatter_.format(*it, ctx);
|
out = value_formatter_.format(*it, ctx);
|
||||||
@ -703,6 +670,123 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
|
||||||
|
const Tuple& tuple;
|
||||||
|
basic_string_view<Char> sep;
|
||||||
|
|
||||||
|
tuple_join_view(const Tuple& t, basic_string_view<Char> s)
|
||||||
|
: tuple(t), sep{s} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
||||||
|
// support in tuple_join. It is disabled by default because of issues with
|
||||||
|
// the dynamic width and precision.
|
||||||
|
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
|
||||||
|
# define FMT_TUPLE_JOIN_SPECIFIERS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename Tuple, typename Char>
|
||||||
|
struct formatter<tuple_join_view<Tuple, Char>, Char,
|
||||||
|
enable_if_t<is_tuple_like<Tuple>::value>> {
|
||||||
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
|
return do_parse(ctx, std::tuple_size<Tuple>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const tuple_join_view<Tuple, Char>& value,
|
||||||
|
FormatContext& ctx) const -> typename FormatContext::iterator {
|
||||||
|
return do_format(value, ctx, std::tuple_size<Tuple>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
decltype(detail::tuple::get_formatters<Tuple, Char>(
|
||||||
|
detail::tuple_index_sequence<Tuple>())) formatters_;
|
||||||
|
|
||||||
|
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
|
||||||
|
std::integral_constant<size_t, 0>)
|
||||||
|
-> const Char* {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
|
||||||
|
std::integral_constant<size_t, N>)
|
||||||
|
-> const Char* {
|
||||||
|
auto end = ctx.begin();
|
||||||
|
#if FMT_TUPLE_JOIN_SPECIFIERS
|
||||||
|
end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
|
||||||
|
if (N > 1) {
|
||||||
|
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
|
||||||
|
if (end != end1)
|
||||||
|
report_error("incompatible format specs for tuple elements");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
|
||||||
|
std::integral_constant<size_t, 0>) const ->
|
||||||
|
typename FormatContext::iterator {
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext, size_t N>
|
||||||
|
auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
|
||||||
|
std::integral_constant<size_t, N>) const ->
|
||||||
|
typename FormatContext::iterator {
|
||||||
|
using std::get;
|
||||||
|
auto out =
|
||||||
|
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
|
||||||
|
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
|
||||||
|
if (N <= 1) return out;
|
||||||
|
out = detail::copy<Char>(value.sep, out);
|
||||||
|
ctx.advance_to(out);
|
||||||
|
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
// Check if T has an interface like a container adaptor (e.g. std::stack,
|
||||||
|
// std::queue, std::priority_queue).
|
||||||
|
template <typename T> class is_container_adaptor_like {
|
||||||
|
template <typename U> static auto check(U* p) -> typename U::container_type;
|
||||||
|
template <typename> static void check(...);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool value =
|
||||||
|
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Container> struct all {
|
||||||
|
const Container& c;
|
||||||
|
auto begin() const -> typename Container::const_iterator { return c.begin(); }
|
||||||
|
auto end() const -> typename Container::const_iterator { return c.end(); }
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<
|
||||||
|
T, Char,
|
||||||
|
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
|
||||||
|
bool_constant<range_format_kind<T, Char>::value ==
|
||||||
|
range_format::disabled>>::value>>
|
||||||
|
: formatter<detail::all<typename T::container_type>, Char> {
|
||||||
|
using all = detail::all<typename T::container_type>;
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
|
struct getter : T {
|
||||||
|
static auto get(const T& v) -> all {
|
||||||
|
return {v.*(&getter::c)}; // Access c through the derived class.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return formatter<all>::format(getter::get(value), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
/// Returns a view that formats the iterator range `[begin, end)` with elements
|
/// Returns a view that formats the iterator range `[begin, end)` with elements
|
||||||
/// separated by `sep`.
|
/// separated by `sep`.
|
||||||
template <typename It, typename Sentinel>
|
template <typename It, typename Sentinel>
|
||||||
@ -724,140 +808,25 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
|
|||||||
* fmt::print("{:02}", fmt::join(v, ", "));
|
* fmt::print("{:02}", fmt::join(v, ", "));
|
||||||
* // Output: 01, 02, 03
|
* // Output: 01, 02, 03
|
||||||
*/
|
*/
|
||||||
template <typename Range>
|
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
|
||||||
auto join(Range&& r, string_view sep)
|
auto join(Range&& r, string_view sep)
|
||||||
-> join_view<decltype(detail::range_begin(r)),
|
-> join_view<decltype(detail::range_begin(r)),
|
||||||
decltype(detail::range_end(r))> {
|
decltype(detail::range_end(r))> {
|
||||||
return {detail::range_begin(r), detail::range_end(r), sep};
|
return {detail::range_begin(r), detail::range_end(r), sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
|
||||||
const std::tuple<T...>& tuple;
|
|
||||||
basic_string_view<Char> sep;
|
|
||||||
|
|
||||||
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
|
||||||
: tuple(t), sep{s} {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
|
||||||
// support in tuple_join. It is disabled by default because of issues with
|
|
||||||
// the dynamic width and precision.
|
|
||||||
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
|
|
||||||
# define FMT_TUPLE_JOIN_SPECIFIERS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename Char, typename... T>
|
|
||||||
struct formatter<tuple_join_view<Char, T...>, Char> {
|
|
||||||
template <typename ParseContext>
|
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const tuple_join_view<Char, T...>& value,
|
|
||||||
FormatContext& ctx) const -> typename FormatContext::iterator {
|
|
||||||
return do_format(value, ctx,
|
|
||||||
std::integral_constant<size_t, sizeof...(T)>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
|
|
||||||
|
|
||||||
template <typename ParseContext>
|
|
||||||
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
|
|
||||||
std::integral_constant<size_t, 0>)
|
|
||||||
-> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename ParseContext, size_t N>
|
|
||||||
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
|
|
||||||
std::integral_constant<size_t, N>)
|
|
||||||
-> decltype(ctx.begin()) {
|
|
||||||
auto end = ctx.begin();
|
|
||||||
#if FMT_TUPLE_JOIN_SPECIFIERS
|
|
||||||
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
|
|
||||||
if (N > 1) {
|
|
||||||
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
|
|
||||||
if (end != end1)
|
|
||||||
report_error("incompatible format specs for tuple elements");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
|
|
||||||
std::integral_constant<size_t, 0>) const ->
|
|
||||||
typename FormatContext::iterator {
|
|
||||||
return ctx.out();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext, size_t N>
|
|
||||||
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
|
||||||
std::integral_constant<size_t, N>) const ->
|
|
||||||
typename FormatContext::iterator {
|
|
||||||
auto out = std::get<sizeof...(T) - N>(formatters_)
|
|
||||||
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
|
|
||||||
if (N <= 1) return out;
|
|
||||||
out = detail::copy<Char>(value.sep, out);
|
|
||||||
ctx.advance_to(out);
|
|
||||||
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
// Check if T has an interface like a container adaptor (e.g. std::stack,
|
|
||||||
// std::queue, std::priority_queue).
|
|
||||||
template <typename T> class is_container_adaptor_like {
|
|
||||||
template <typename U> static auto check(U* p) -> typename U::container_type;
|
|
||||||
template <typename> static void check(...);
|
|
||||||
|
|
||||||
public:
|
|
||||||
static constexpr const bool value =
|
|
||||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Container> struct all {
|
|
||||||
const Container& c;
|
|
||||||
auto begin() const -> typename Container::const_iterator { return c.begin(); }
|
|
||||||
auto end() const -> typename Container::const_iterator { return c.end(); }
|
|
||||||
};
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <typename T, typename Char>
|
|
||||||
struct formatter<
|
|
||||||
T, Char,
|
|
||||||
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
|
|
||||||
bool_constant<range_format_kind<T, Char>::value ==
|
|
||||||
range_format::disabled>>::value>>
|
|
||||||
: formatter<detail::all<typename T::container_type>, Char> {
|
|
||||||
using all = detail::all<typename T::container_type>;
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
|
|
||||||
struct getter : T {
|
|
||||||
static auto get(const T& t) -> all {
|
|
||||||
return {t.*(&getter::c)}; // Access c through the derived class.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return formatter<all>::format(getter::get(t), ctx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
FMT_BEGIN_EXPORT
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object that formats `std::tuple` with elements separated by `sep`.
|
* Returns an object that formats `std::tuple` with elements separated by `sep`.
|
||||||
*
|
*
|
||||||
* **Example**:
|
* **Example**:
|
||||||
*
|
*
|
||||||
* auto t = std::tuple<int, char>{1, 'a'};
|
* auto t = std::tuple<int, char>(1, 'a');
|
||||||
* fmt::print("{}", fmt::join(t, ", "));
|
* fmt::print("{}", fmt::join(t, ", "));
|
||||||
* // Output: 1, a
|
* // Output: 1, a
|
||||||
*/
|
*/
|
||||||
template <typename... T>
|
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
|
||||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep)
|
||||||
-> tuple_join_view<char, T...> {
|
-> tuple_join_view<Tuple, char> {
|
||||||
return {tuple, sep};
|
return {tuple, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,18 +15,18 @@
|
|||||||
# include <atomic>
|
# include <atomic>
|
||||||
# include <bitset>
|
# include <bitset>
|
||||||
# include <complex>
|
# include <complex>
|
||||||
# include <cstdlib>
|
|
||||||
# include <exception>
|
# include <exception>
|
||||||
|
# include <functional> // std::reference_wrapper
|
||||||
# include <memory>
|
# include <memory>
|
||||||
# include <thread>
|
# include <thread>
|
||||||
# include <type_traits>
|
# include <type_traits>
|
||||||
# include <typeinfo>
|
# include <typeinfo> // std::type_info
|
||||||
# include <utility>
|
# include <utility> // std::make_index_sequence
|
||||||
# include <vector>
|
|
||||||
|
|
||||||
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
|
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
|
||||||
# if FMT_CPLUSPLUS >= 201703L
|
# if FMT_CPLUSPLUS >= 201703L
|
||||||
# if FMT_HAS_INCLUDE(<filesystem>)
|
# if FMT_HAS_INCLUDE(<filesystem>) && \
|
||||||
|
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
|
||||||
# include <filesystem>
|
# include <filesystem>
|
||||||
# endif
|
# endif
|
||||||
# if FMT_HAS_INCLUDE(<variant>)
|
# if FMT_HAS_INCLUDE(<variant>)
|
||||||
@ -60,35 +60,36 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
|
#ifdef FMT_CPP_LIB_FILESYSTEM
|
||||||
#ifndef FMT_CPP_LIB_FILESYSTEM
|
// Use the provided definition.
|
||||||
# ifdef __cpp_lib_filesystem
|
#elif defined(__cpp_lib_filesystem)
|
||||||
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
|
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
|
||||||
# else
|
#else
|
||||||
# define FMT_CPP_LIB_FILESYSTEM 0
|
# define FMT_CPP_LIB_FILESYSTEM 0
|
||||||
# endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef FMT_CPP_LIB_VARIANT
|
#ifdef FMT_CPP_LIB_VARIANT
|
||||||
# ifdef __cpp_lib_variant
|
// Use the provided definition.
|
||||||
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
|
#elif defined(__cpp_lib_variant)
|
||||||
# else
|
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
|
||||||
# define FMT_CPP_LIB_VARIANT 0
|
#else
|
||||||
# endif
|
# define FMT_CPP_LIB_VARIANT 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
#if FMT_CPP_LIB_FILESYSTEM
|
#if FMT_CPP_LIB_FILESYSTEM
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
template <typename Char, typename PathChar>
|
template <typename Char, typename PathChar>
|
||||||
auto get_path_string(const std::filesystem::path& p,
|
auto get_path_string(const std::filesystem::path& p,
|
||||||
const std::basic_string<PathChar>& native) {
|
const std::basic_string<PathChar>& native) {
|
||||||
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
|
if constexpr (std::is_same_v<Char, char> &&
|
||||||
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
|
std::is_same_v<PathChar, wchar_t>) {
|
||||||
else
|
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf);
|
||||||
|
} else {
|
||||||
return p.string<Char>();
|
return p.string<Char>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename PathChar>
|
template <typename Char, typename PathChar>
|
||||||
@ -109,9 +110,180 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
||||||
|
|
||||||
|
template <typename Char, typename OutputIt, typename T, typename FormatContext>
|
||||||
|
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
|
||||||
|
-> OutputIt {
|
||||||
|
if constexpr (has_to_string_view<T>::value)
|
||||||
|
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||||
|
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
||||||
|
|
||||||
|
formatter<std::remove_cv_t<T>, Char> underlying;
|
||||||
|
maybe_set_debug_format(underlying, true);
|
||||||
|
return underlying.format(v, ctx);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FMT_CPP_LIB_VARIANT
|
||||||
|
|
||||||
|
template <typename> struct is_variant_like_ : std::false_type {};
|
||||||
|
template <typename... Types>
|
||||||
|
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename Variant, typename Char> class is_variant_formattable {
|
||||||
|
template <size_t... Is>
|
||||||
|
static auto check(std::index_sequence<Is...>) -> std::conjunction<
|
||||||
|
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool value = decltype(check(
|
||||||
|
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FMT_CPP_LIB_VARIANT
|
||||||
|
|
||||||
|
#if FMT_USE_RTTI
|
||||||
|
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
|
||||||
|
char* begin) -> string_view {
|
||||||
|
// Normalization of stdlib inline namespace names.
|
||||||
|
// libc++ inline namespaces.
|
||||||
|
// std::__1::* -> std::*
|
||||||
|
// std::__1::__fs::* -> std::*
|
||||||
|
// libstdc++ inline namespaces.
|
||||||
|
// std::__cxx11::* -> std::*
|
||||||
|
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||||
|
if (demangled_name_view.starts_with("std::")) {
|
||||||
|
char* to = begin + 5; // std::
|
||||||
|
for (const char *from = to, *end = begin + demangled_name_view.size();
|
||||||
|
from < end;) {
|
||||||
|
// This is safe, because demangled_name is NUL-terminated.
|
||||||
|
if (from[0] == '_' && from[1] == '_') {
|
||||||
|
const char* next = from + 1;
|
||||||
|
while (next < end && *next != ':') next++;
|
||||||
|
if (next[0] == ':' && next[1] == ':') {
|
||||||
|
from = next + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*to++ = *from++;
|
||||||
|
}
|
||||||
|
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||||
|
}
|
||||||
|
return demangled_name_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class OutputIt>
|
||||||
|
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
|
||||||
|
-> OutputIt {
|
||||||
|
const string_view demangled_name(abi_name_view);
|
||||||
|
for (size_t i = 0; i < demangled_name.size(); ++i) {
|
||||||
|
auto sub = demangled_name;
|
||||||
|
sub.remove_prefix(i);
|
||||||
|
if (sub.starts_with("enum ")) {
|
||||||
|
i += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sub.starts_with("class ") || sub.starts_with("union ")) {
|
||||||
|
i += 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sub.starts_with("struct ")) {
|
||||||
|
i += 6;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||||
|
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||||
|
int status = 0;
|
||||||
|
size_t size = 0;
|
||||||
|
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||||
|
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
|
||||||
|
|
||||||
|
string_view demangled_name_view;
|
||||||
|
if (demangled_name_ptr) {
|
||||||
|
demangled_name_view = normalize_libcxx_inline_namespaces(
|
||||||
|
demangled_name_ptr.get(), demangled_name_ptr.get());
|
||||||
|
} else {
|
||||||
|
demangled_name_view = string_view(ti.name());
|
||||||
|
}
|
||||||
|
return detail::write_bytes<char>(out, demangled_name_view);
|
||||||
|
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
|
||||||
|
return normalize_msvc_abi_name(ti.name(), out);
|
||||||
|
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
|
||||||
|
const string_view demangled_name = ti.name();
|
||||||
|
std::string name_copy(demangled_name.size(), '\0');
|
||||||
|
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
|
||||||
|
// front of types
|
||||||
|
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
|
||||||
|
name_copy.end());
|
||||||
|
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
|
||||||
|
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
|
||||||
|
// environments, we need to eliminate both of them.
|
||||||
|
const string_view normalized_name =
|
||||||
|
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
|
||||||
|
return detail::write_bytes<char>(out, normalized_name);
|
||||||
|
# else
|
||||||
|
return detail::write_bytes<char>(out, string_view(ti.name()));
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // FMT_USE_RTTI
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_flip : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T> struct is_bit_reference_like {
|
||||||
|
static constexpr bool value = std::is_convertible<T, bool>::value &&
|
||||||
|
std::is_nothrow_assignable<T, bool>::value &&
|
||||||
|
has_flip<T>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Workaround for libc++ incompatibility with C++ standard.
|
||||||
|
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||||
|
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
|
||||||
|
template <typename C>
|
||||||
|
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||||
|
static constexpr bool value = true;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_format_as : std::false_type {};
|
||||||
|
template <typename T>
|
||||||
|
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_format_as_member : std::false_type {};
|
||||||
|
template <typename T>
|
||||||
|
struct has_format_as_member<
|
||||||
|
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
FMT_EXPORT
|
template <typename T, typename Deleter>
|
||||||
|
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
|
||||||
|
return p.get();
|
||||||
|
}
|
||||||
|
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
||||||
|
return p.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_CPP_LIB_FILESYSTEM
|
||||||
|
|
||||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||||
private:
|
private:
|
||||||
format_specs specs_;
|
format_specs specs_;
|
||||||
@ -122,14 +294,16 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
|
|||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||||
|
|
||||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
|
||||||
auto it = ctx.begin(), end = ctx.end();
|
auto it = ctx.begin(), end = ctx.end();
|
||||||
if (it == end) return it;
|
if (it == end) return it;
|
||||||
|
|
||||||
it = detail::parse_align(it, end, specs_);
|
it = detail::parse_align(it, end, specs_);
|
||||||
if (it == end) return it;
|
if (it == end) return it;
|
||||||
|
|
||||||
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
Char c = *it;
|
||||||
|
if ((c >= '0' && c <= '9') || c == '{')
|
||||||
|
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
|
||||||
if (it != end && *it == '?') {
|
if (it != end && *it == '?') {
|
||||||
debug_ = true;
|
debug_ = true;
|
||||||
++it;
|
++it;
|
||||||
@ -145,8 +319,8 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
|
|||||||
!path_type_ ? p.native()
|
!path_type_ ? p.native()
|
||||||
: p.generic_string<std::filesystem::path::value_type>();
|
: p.generic_string<std::filesystem::path::value_type>();
|
||||||
|
|
||||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||||
ctx);
|
ctx);
|
||||||
if (!debug_) {
|
if (!debug_) {
|
||||||
auto s = detail::get_path_string<Char>(p, path_string);
|
auto s = detail::get_path_string<Char>(p, path_string);
|
||||||
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
||||||
@ -174,24 +348,20 @@ class path : public std::filesystem::path {
|
|||||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
|
||||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
template <size_t N, typename Char>
|
||||||
FMT_EXPORT
|
struct formatter<std::bitset<N>, Char>
|
||||||
template <std::size_t N, typename Char>
|
: nested_formatter<basic_string_view<Char>, Char> {
|
||||||
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
|
|
||||||
private:
|
private:
|
||||||
// Functor because C++11 doesn't support generic lambdas.
|
// This is a functor because C++11 doesn't support generic lambdas.
|
||||||
struct writer {
|
struct writer {
|
||||||
const std::bitset<N>& bs;
|
const std::bitset<N>& bs;
|
||||||
|
|
||||||
template <typename OutputIt>
|
template <typename OutputIt>
|
||||||
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
|
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
|
||||||
for (auto pos = N; pos > 0; --pos) {
|
for (auto pos = N; pos > 0; --pos)
|
||||||
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
|
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -200,41 +370,28 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
|
|||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
|
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
return write_padded(ctx, writer{bs});
|
return this->write_padded(ctx, writer{bs});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||||
FMT_END_NAMESPACE
|
|
||||||
|
|
||||||
#ifdef __cpp_lib_optional
|
#ifdef __cpp_lib_optional
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<std::optional<T>, Char,
|
struct formatter<std::optional<T>, Char,
|
||||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||||
private:
|
private:
|
||||||
formatter<T, Char> underlying_;
|
formatter<std::remove_cv_t<T>, Char> underlying_;
|
||||||
static constexpr basic_string_view<Char> optional =
|
static constexpr basic_string_view<Char> optional =
|
||||||
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
||||||
'('>{};
|
'('>{};
|
||||||
static constexpr basic_string_view<Char> none =
|
static constexpr basic_string_view<Char> none =
|
||||||
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
||||||
|
|
||||||
template <class U>
|
|
||||||
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
|
|
||||||
-> decltype(u.set_debug_format(set)) {
|
|
||||||
u.set_debug_format(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class U>
|
|
||||||
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
|
||||||
maybe_set_debug_format(underlying_, true);
|
detail::maybe_set_debug_format(underlying_, true);
|
||||||
return underlying_.parse(ctx);
|
return underlying_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,37 +407,15 @@ struct formatter<std::optional<T>, Char,
|
|||||||
return detail::write(out, ')');
|
return detail::write(out, ')');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
|
||||||
#endif // __cpp_lib_optional
|
#endif // __cpp_lib_optional
|
||||||
|
|
||||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
template <typename Char, typename OutputIt, typename T>
|
|
||||||
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
|
|
||||||
if constexpr (has_to_string_view<T>::value)
|
|
||||||
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
|
||||||
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
|
||||||
return write<Char>(out, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cpp_lib_expected
|
#ifdef __cpp_lib_expected
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename T, typename E, typename Char>
|
template <typename T, typename E, typename Char>
|
||||||
struct formatter<std::expected<T, E>, Char,
|
struct formatter<std::expected<T, E>, Char,
|
||||||
std::enable_if_t<is_formattable<T, Char>::value &&
|
std::enable_if_t<(std::is_void<T>::value ||
|
||||||
|
is_formattable<T, Char>::value) &&
|
||||||
is_formattable<E, Char>::value>> {
|
is_formattable<E, Char>::value>> {
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,25 +426,21 @@ struct formatter<std::expected<T, E>, Char,
|
|||||||
|
|
||||||
if (value.has_value()) {
|
if (value.has_value()) {
|
||||||
out = detail::write<Char>(out, "expected(");
|
out = detail::write<Char>(out, "expected(");
|
||||||
out = detail::write_escaped_alternative<Char>(out, *value);
|
if constexpr (!std::is_void<T>::value)
|
||||||
|
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
|
||||||
} else {
|
} else {
|
||||||
out = detail::write<Char>(out, "unexpected(");
|
out = detail::write<Char>(out, "unexpected(");
|
||||||
out = detail::write_escaped_alternative<Char>(out, value.error());
|
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
|
||||||
}
|
}
|
||||||
*out++ = ')';
|
*out++ = ')';
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
|
||||||
#endif // __cpp_lib_expected
|
#endif // __cpp_lib_expected
|
||||||
|
|
||||||
#ifdef __cpp_lib_source_location
|
#ifdef __cpp_lib_source_location
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
FMT_EXPORT
|
|
||||||
template <> struct formatter<std::source_location> {
|
template <> struct formatter<std::source_location> {
|
||||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const std::source_location& loc, FormatContext& ctx) const
|
auto format(const std::source_location& loc, FormatContext& ctx) const
|
||||||
@ -325,48 +456,16 @@ template <> struct formatter<std::source_location> {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FMT_CPP_LIB_VARIANT
|
#if FMT_CPP_LIB_VARIANT
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using variant_index_sequence =
|
|
||||||
std::make_index_sequence<std::variant_size<T>::value>;
|
|
||||||
|
|
||||||
template <typename> struct is_variant_like_ : std::false_type {};
|
|
||||||
template <typename... Types>
|
|
||||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
|
||||||
|
|
||||||
// formattable element check.
|
|
||||||
template <typename T, typename C> class is_variant_formattable_ {
|
|
||||||
template <std::size_t... Is>
|
|
||||||
static std::conjunction<
|
|
||||||
is_formattable<std::variant_alternative_t<Is, T>, C>...>
|
|
||||||
check(std::index_sequence<Is...>);
|
|
||||||
|
|
||||||
public:
|
|
||||||
static constexpr const bool value =
|
|
||||||
decltype(check(variant_index_sequence<T>{}))::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <typename T> struct is_variant_like {
|
template <typename T> struct is_variant_like {
|
||||||
static constexpr const bool value = detail::is_variant_like_<T>::value;
|
static constexpr bool value = detail::is_variant_like_<T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename C> struct is_variant_formattable {
|
|
||||||
static constexpr const bool value =
|
|
||||||
detail::is_variant_formattable_<T, C>::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename Char> struct formatter<std::monostate, Char> {
|
template <typename Char> struct formatter<std::monostate, Char> {
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,14 +476,12 @@ template <typename Char> struct formatter<std::monostate, Char> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename Variant, typename Char>
|
template <typename Variant, typename Char>
|
||||||
struct formatter<
|
struct formatter<Variant, Char,
|
||||||
Variant, Char,
|
std::enable_if_t<std::conjunction_v<
|
||||||
std::enable_if_t<std::conjunction_v<
|
is_variant_like<Variant>,
|
||||||
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
|
detail::is_variant_formattable<Variant, Char>>>> {
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +494,7 @@ struct formatter<
|
|||||||
FMT_TRY {
|
FMT_TRY {
|
||||||
std::visit(
|
std::visit(
|
||||||
[&](const auto& v) {
|
[&](const auto& v) {
|
||||||
out = detail::write_escaped_alternative<Char>(out, v);
|
out = detail::write_escaped_alternative<Char>(out, v, ctx);
|
||||||
},
|
},
|
||||||
value);
|
value);
|
||||||
}
|
}
|
||||||
@ -408,128 +505,87 @@ struct formatter<
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
|
||||||
#endif // FMT_CPP_LIB_VARIANT
|
#endif // FMT_CPP_LIB_VARIANT
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
template <> struct formatter<std::error_code> {
|
||||||
FMT_EXPORT
|
private:
|
||||||
template <typename Char> struct formatter<std::error_code, Char> {
|
format_specs specs_;
|
||||||
template <typename ParseContext>
|
detail::arg_ref<char> width_ref_;
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
bool debug_ = false;
|
||||||
return ctx.begin();
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||||
|
|
||||||
|
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||||
|
auto it = ctx.begin(), end = ctx.end();
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
it = detail::parse_align(it, end, specs_);
|
||||||
|
|
||||||
|
char c = *it;
|
||||||
|
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
|
||||||
|
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
|
||||||
|
|
||||||
|
if (it != end && *it == '?') {
|
||||||
|
debug_ = true;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
if (it != end && *it == 's') {
|
||||||
|
specs_.set_type(presentation_type::string);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
FMT_CONSTEXPR20 auto format(const std::error_code& ec,
|
||||||
-> decltype(ctx.out()) {
|
FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
auto out = ctx.out();
|
auto specs = specs_;
|
||||||
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs());
|
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||||
out = detail::write<Char>(out, Char(':'));
|
ctx);
|
||||||
out = detail::write<Char>(out, ec.value());
|
auto buf = memory_buffer();
|
||||||
return out;
|
if (specs_.type() == presentation_type::string) {
|
||||||
|
buf.append(ec.message());
|
||||||
|
} else {
|
||||||
|
buf.append(string_view(ec.category().name()));
|
||||||
|
buf.push_back(':');
|
||||||
|
detail::write<char>(appender(buf), ec.value());
|
||||||
|
}
|
||||||
|
auto quoted = memory_buffer();
|
||||||
|
auto str = string_view(buf.data(), buf.size());
|
||||||
|
if (debug_) {
|
||||||
|
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
|
||||||
|
str = string_view(quoted.data(), quoted.size());
|
||||||
|
}
|
||||||
|
return detail::write<char>(ctx.out(), str, specs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#if FMT_USE_RTTI
|
#if FMT_USE_RTTI
|
||||||
namespace detail {
|
template <> struct formatter<std::type_info> {
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
|
||||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
|
||||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
|
||||||
int status = 0;
|
|
||||||
std::size_t size = 0;
|
|
||||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
|
||||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
|
||||||
|
|
||||||
string_view demangled_name_view;
|
|
||||||
if (demangled_name_ptr) {
|
|
||||||
demangled_name_view = demangled_name_ptr.get();
|
|
||||||
|
|
||||||
// Normalization of stdlib inline namespace names.
|
|
||||||
// libc++ inline namespaces.
|
|
||||||
// std::__1::* -> std::*
|
|
||||||
// std::__1::__fs::* -> std::*
|
|
||||||
// libstdc++ inline namespaces.
|
|
||||||
// std::__cxx11::* -> std::*
|
|
||||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
|
||||||
if (demangled_name_view.starts_with("std::")) {
|
|
||||||
char* begin = demangled_name_ptr.get();
|
|
||||||
char* to = begin + 5; // std::
|
|
||||||
for (char *from = to, *end = begin + demangled_name_view.size();
|
|
||||||
from < end;) {
|
|
||||||
// This is safe, because demangled_name is NUL-terminated.
|
|
||||||
if (from[0] == '_' && from[1] == '_') {
|
|
||||||
char* next = from + 1;
|
|
||||||
while (next < end && *next != ':') next++;
|
|
||||||
if (next[0] == ':' && next[1] == ':') {
|
|
||||||
from = next + 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*to++ = *from++;
|
|
||||||
}
|
|
||||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
demangled_name_view = string_view(ti.name());
|
|
||||||
}
|
|
||||||
return detail::write_bytes<Char>(out, demangled_name_view);
|
|
||||||
# elif FMT_MSC_VERSION
|
|
||||||
const string_view demangled_name(ti.name());
|
|
||||||
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
|
|
||||||
auto sub = demangled_name;
|
|
||||||
sub.remove_prefix(i);
|
|
||||||
if (sub.starts_with("enum ")) {
|
|
||||||
i += 4;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (sub.starts_with("class ") || sub.starts_with("union ")) {
|
|
||||||
i += 5;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (sub.starts_with("struct ")) {
|
|
||||||
i += 6;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
# else
|
|
||||||
return detail::write_bytes<Char>(out, string_view(ti.name()));
|
|
||||||
# endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename Char>
|
|
||||||
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
|
|
||||||
> {
|
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||||
-> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Context>
|
template <typename Context>
|
||||||
auto format(const std::type_info& ti, Context& ctx) const
|
auto format(const std::type_info& ti, Context& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
return detail::write_demangled_name<Char>(ctx.out(), ti);
|
return detail::write_demangled_name(ctx.out(), ti);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
#endif // FMT_USE_RTTI
|
||||||
|
|
||||||
FMT_EXPORT
|
template <typename T>
|
||||||
template <typename T, typename Char>
|
|
||||||
struct formatter<
|
struct formatter<
|
||||||
T, Char, // DEPRECATED! Mixing code unit types.
|
T, char,
|
||||||
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||||
private:
|
private:
|
||||||
bool with_typename_ = false;
|
bool with_typename_ = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||||
-> decltype(ctx.begin()) {
|
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
auto end = ctx.end();
|
auto end = ctx.end();
|
||||||
if (it == end || *it == '}') return it;
|
if (it == end || *it == '}') return it;
|
||||||
@ -546,47 +602,18 @@ struct formatter<
|
|||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
#if FMT_USE_RTTI
|
#if FMT_USE_RTTI
|
||||||
if (with_typename_) {
|
if (with_typename_) {
|
||||||
out = detail::write_demangled_name<Char>(out, typeid(ex));
|
out = detail::write_demangled_name(out, typeid(ex));
|
||||||
*out++ = ':';
|
*out++ = ':';
|
||||||
*out++ = ' ';
|
*out++ = ' ';
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return detail::write_bytes<Char>(out, string_view(ex.what()));
|
return detail::write_bytes<char>(out, string_view(ex.what()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
template <typename T, typename Enable = void>
|
|
||||||
struct has_flip : std::false_type {};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
|
||||||
: std::true_type {};
|
|
||||||
|
|
||||||
template <typename T> struct is_bit_reference_like {
|
|
||||||
static constexpr const bool value =
|
|
||||||
std::is_convertible<T, bool>::value &&
|
|
||||||
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef _LIBCPP_VERSION
|
|
||||||
|
|
||||||
// Workaround for libc++ incompatibility with C++ standard.
|
|
||||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
|
||||||
template <typename C>
|
|
||||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
|
||||||
static constexpr const bool value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
// We can't use std::vector<bool, Allocator>::reference and
|
// We can't use std::vector<bool, Allocator>::reference and
|
||||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||||
// in partial specialization.
|
// in partial specialization.
|
||||||
FMT_EXPORT
|
|
||||||
template <typename BitRef, typename Char>
|
template <typename BitRef, typename Char>
|
||||||
struct formatter<BitRef, Char,
|
struct formatter<BitRef, Char,
|
||||||
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||||
@ -598,15 +625,6 @@ struct formatter<BitRef, Char,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename Deleter>
|
|
||||||
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
|
|
||||||
return p.get();
|
|
||||||
}
|
|
||||||
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
|
||||||
return p.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_EXPORT
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<std::atomic<T>, Char,
|
struct formatter<std::atomic<T>, Char,
|
||||||
enable_if_t<is_formattable<T, Char>::value>>
|
enable_if_t<is_formattable<T, Char>::value>>
|
||||||
@ -619,7 +637,6 @@ struct formatter<std::atomic<T>, Char,
|
|||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cpp_lib_atomic_flag_test
|
#ifdef __cpp_lib_atomic_flag_test
|
||||||
FMT_EXPORT
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
@ -630,7 +647,11 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
|||||||
};
|
};
|
||||||
#endif // __cpp_lib_atomic_flag_test
|
#endif // __cpp_lib_atomic_flag_test
|
||||||
|
|
||||||
FMT_EXPORT
|
template <typename T> struct is_tuple_like;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_tuple_like<std::complex<T>> : std::false_type {};
|
||||||
|
|
||||||
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
||||||
private:
|
private:
|
||||||
detail::dynamic_format_specs<Char> specs_;
|
detail::dynamic_format_specs<Char> specs_;
|
||||||
@ -643,7 +664,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
|||||||
if (c.real() != 0) {
|
if (c.real() != 0) {
|
||||||
*out++ = Char('(');
|
*out++ = Char('(');
|
||||||
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
|
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
|
||||||
specs.sign = sign::plus;
|
specs.set_sign(sign::plus);
|
||||||
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
|
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
|
||||||
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
|
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
|
||||||
*out++ = Char('i');
|
*out++ = Char('i');
|
||||||
@ -657,8 +678,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||||
-> decltype(ctx.begin()) {
|
|
||||||
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
|
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
|
||||||
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
|
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
|
||||||
detail::type_constant<T, Char>::value);
|
detail::type_constant<T, Char>::value);
|
||||||
@ -668,12 +688,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
|||||||
auto format(const std::complex<T>& c, FormatContext& ctx) const
|
auto format(const std::complex<T>& c, FormatContext& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
auto specs = specs_;
|
auto specs = specs_;
|
||||||
if (specs.width_ref.kind != detail::arg_id_kind::none ||
|
if (specs.dynamic()) {
|
||||||
specs.precision_ref.kind != detail::arg_id_kind::none) {
|
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
|
||||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
|
specs.width_ref, ctx);
|
||||||
specs.width_ref, ctx);
|
detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
|
||||||
detail::handle_dynamic_spec<detail::precision_checker>(
|
specs.precision_ref, ctx);
|
||||||
specs.precision, specs.precision_ref, ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
|
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
|
||||||
@ -681,12 +700,12 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
|||||||
|
|
||||||
auto outer_specs = format_specs();
|
auto outer_specs = format_specs();
|
||||||
outer_specs.width = specs.width;
|
outer_specs.width = specs.width;
|
||||||
outer_specs.fill = specs.fill;
|
outer_specs.copy_fill_from(specs);
|
||||||
outer_specs.align = specs.align;
|
outer_specs.set_align(specs.align());
|
||||||
|
|
||||||
specs.width = 0;
|
specs.width = 0;
|
||||||
specs.fill = {};
|
specs.set_fill({});
|
||||||
specs.align = align::none;
|
specs.set_align(align::none);
|
||||||
|
|
||||||
do_format(c, specs, ctx, basic_appender<Char>(buf));
|
do_format(c, specs, ctx, basic_appender<Char>(buf));
|
||||||
return detail::write<Char>(ctx.out(),
|
return detail::write<Char>(ctx.out(),
|
||||||
@ -695,5 +714,21 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<std::reference_wrapper<T>, Char,
|
||||||
|
// Guard against format_as because reference_wrapper is
|
||||||
|
// implicitly convertible to T&.
|
||||||
|
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
|
||||||
|
!detail::has_format_as<T>::value &&
|
||||||
|
!detail::has_format_as_member<T>::value>>
|
||||||
|
: formatter<remove_cvref_t<T>, Char> {
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_STD_H_
|
#endif // FMT_STD_H_
|
||||||
|
|||||||
@ -10,11 +10,12 @@
|
|||||||
|
|
||||||
#include "color.h"
|
#include "color.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
#include "ostream.h"
|
||||||
#include "ranges.h"
|
#include "ranges.h"
|
||||||
|
|
||||||
#ifndef FMT_MODULE
|
#ifndef FMT_MODULE
|
||||||
# include <cwchar>
|
# include <cwchar>
|
||||||
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
# if FMT_USE_LOCALE
|
||||||
# include <locale>
|
# include <locale>
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
@ -34,7 +35,8 @@ struct format_string_char<
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename S>
|
template <typename S>
|
||||||
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> {
|
struct format_string_char<
|
||||||
|
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
|
||||||
using type = typename S::char_type;
|
using type = typename S::char_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ using format_string_char_t = typename format_string_char<S>::type;
|
|||||||
|
|
||||||
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
|
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
|
||||||
const format_specs& specs, locale_ref loc) -> bool {
|
const format_specs& specs, locale_ref loc) -> bool {
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#if FMT_USE_LOCALE
|
||||||
auto& numpunct =
|
auto& numpunct =
|
||||||
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
|
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
|
||||||
auto separator = std::wstring();
|
auto separator = std::wstring();
|
||||||
@ -53,36 +55,72 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
|
|||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
|
||||||
|
basic_format_args<buffered_context<Char>> args,
|
||||||
|
locale_ref loc = {}) {
|
||||||
|
static_assert(!std::is_same<Char, char>::value, "");
|
||||||
|
auto out = basic_appender<Char>(buf);
|
||||||
|
parse_format_string(
|
||||||
|
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
|
||||||
|
}
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
FMT_BEGIN_EXPORT
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
using wstring_view = basic_string_view<wchar_t>;
|
using wstring_view = basic_string_view<wchar_t>;
|
||||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
using wformat_parse_context = parse_context<wchar_t>;
|
||||||
using wformat_context = buffered_context<wchar_t>;
|
using wformat_context = buffered_context<wchar_t>;
|
||||||
using wformat_args = basic_format_args<wformat_context>;
|
using wformat_args = basic_format_args<wformat_context>;
|
||||||
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||||
|
|
||||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
template <typename Char, typename... T> struct basic_fstring {
|
||||||
// Workaround broken conversion on older gcc.
|
private:
|
||||||
template <typename... Args> using wformat_string = wstring_view;
|
basic_string_view<Char> str_;
|
||||||
inline auto runtime(wstring_view s) -> wstring_view { return s; }
|
|
||||||
#else
|
static constexpr int num_static_named_args =
|
||||||
template <typename... Args>
|
detail::count_static_named_args<T...>();
|
||||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
|
||||||
|
using checker = detail::format_string_checker<
|
||||||
|
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
|
||||||
|
num_static_named_args != detail::count_named_args<T...>()>;
|
||||||
|
|
||||||
|
using arg_pack = detail::arg_pack<T...>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using t = basic_fstring;
|
||||||
|
|
||||||
|
template <typename S,
|
||||||
|
FMT_ENABLE_IF(
|
||||||
|
std::is_convertible<const S&, basic_string_view<Char>>::value)>
|
||||||
|
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
|
||||||
|
if (FMT_USE_CONSTEVAL)
|
||||||
|
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
|
||||||
|
}
|
||||||
|
template <typename S,
|
||||||
|
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
|
||||||
|
std::is_same<typename S::char_type, Char>::value)>
|
||||||
|
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
|
||||||
|
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
|
||||||
|
FMT_CONSTEXPR int ignore =
|
||||||
|
(parse_format_string(sv, checker(sv, arg_pack())), 0);
|
||||||
|
detail::ignore_unused(ignore);
|
||||||
|
}
|
||||||
|
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
|
||||||
|
|
||||||
|
operator basic_string_view<Char>() const { return str_; }
|
||||||
|
auto get() const -> basic_string_view<Char> { return str_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename... T>
|
||||||
|
using basic_format_string = basic_fstring<Char, T...>;
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
|
||||||
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
||||||
return {{s}};
|
return {{s}};
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
template <> struct is_char<wchar_t> : std::true_type {};
|
|
||||||
template <> struct is_char<char16_t> : std::true_type {};
|
|
||||||
template <> struct is_char<char32_t> : std::true_type {};
|
|
||||||
|
|
||||||
#ifdef __cpp_char8_t
|
|
||||||
template <>
|
|
||||||
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
constexpr auto make_wformat_args(T&... args)
|
constexpr auto make_wformat_args(T&... args)
|
||||||
@ -90,14 +128,13 @@ constexpr auto make_wformat_args(T&... args)
|
|||||||
return fmt::make_format_args<wformat_context>(args...);
|
return fmt::make_format_args<wformat_context>(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||||
inline namespace literals {
|
inline namespace literals {
|
||||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
|
inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
|
||||||
constexpr auto operator""_a(const wchar_t* s, size_t)
|
|
||||||
-> detail::udl_arg<wchar_t> {
|
|
||||||
return {s};
|
return {s};
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
} // namespace literals
|
} // namespace literals
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename It, typename Sentinel>
|
template <typename It, typename Sentinel>
|
||||||
auto join(It begin, Sentinel end, wstring_view sep)
|
auto join(It begin, Sentinel end, wstring_view sep)
|
||||||
@ -105,9 +142,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
|
|||||||
return {begin, end, sep};
|
return {begin, end, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Range>
|
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
|
||||||
auto join(Range&& range, wstring_view sep)
|
auto join(Range&& range, wstring_view sep)
|
||||||
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
-> join_view<decltype(std::begin(range)), decltype(std::end(range)),
|
||||||
wchar_t> {
|
wchar_t> {
|
||||||
return join(std::begin(range), std::end(range), sep);
|
return join(std::begin(range), std::end(range), sep);
|
||||||
}
|
}
|
||||||
@ -118,19 +155,19 @@ auto join(std::initializer_list<T> list, wstring_view sep)
|
|||||||
return join(std::begin(list), std::end(list), sep);
|
return join(std::begin(list), std::end(list), sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
|
||||||
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
|
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
|
||||||
-> tuple_join_view<wchar_t, T...> {
|
-> tuple_join_view<Tuple, wchar_t> {
|
||||||
return {tuple, sep};
|
return {tuple, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||||
auto vformat(basic_string_view<Char> format_str,
|
auto vformat(basic_string_view<Char> fmt,
|
||||||
typename detail::vformat_args<Char>::type args)
|
basic_format_args<buffered_context<Char>> args)
|
||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
auto buf = basic_memory_buffer<Char>();
|
auto buf = basic_memory_buffer<Char>();
|
||||||
detail::vformat_to(buf, format_str, args);
|
detail::vformat_to(buf, fmt, args);
|
||||||
return to_string(buf);
|
return {buf.data(), buf.size()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
@ -151,40 +188,38 @@ template <typename S, typename... T,
|
|||||||
typename Char = detail::format_string_char_t<S>,
|
typename Char = detail::format_string_char_t<S>,
|
||||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||||
!std::is_same<Char, wchar_t>::value)>
|
!std::is_same<Char, wchar_t>::value)>
|
||||||
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
|
auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
|
||||||
return vformat(detail::to_string_view(format_str),
|
return vformat(detail::to_string_view(fmt),
|
||||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Locale, typename S,
|
template <typename S, typename Char = detail::format_string_char_t<S>,
|
||||||
typename Char = detail::format_string_char_t<S>,
|
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
inline auto vformat(locale_ref loc, const S& fmt,
|
||||||
detail::is_exotic_char<Char>::value)>
|
basic_format_args<buffered_context<Char>> args)
|
||||||
inline auto vformat(const Locale& loc, const S& format_str,
|
|
||||||
typename detail::vformat_args<Char>::type args)
|
|
||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
return detail::vformat(loc, detail::to_string_view(format_str), args);
|
auto buf = basic_memory_buffer<Char>();
|
||||||
|
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
|
||||||
|
return {buf.data(), buf.size()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Locale, typename S, typename... T,
|
template <typename S, typename... T,
|
||||||
typename Char = detail::format_string_char_t<S>,
|
typename Char = detail::format_string_char_t<S>,
|
||||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||||
detail::is_exotic_char<Char>::value)>
|
inline auto format(locale_ref loc, const S& fmt, T&&... args)
|
||||||
inline auto format(const Locale& loc, const S& format_str, T&&... args)
|
|
||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
return detail::vformat(
|
return vformat(loc, detail::to_string_view(fmt),
|
||||||
loc, detail::to_string_view(format_str),
|
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename S,
|
template <typename OutputIt, typename S,
|
||||||
typename Char = detail::format_string_char_t<S>,
|
typename Char = detail::format_string_char_t<S>,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
detail::is_exotic_char<Char>::value)>
|
detail::is_exotic_char<Char>::value)>
|
||||||
auto vformat_to(OutputIt out, const S& format_str,
|
auto vformat_to(OutputIt out, const S& fmt,
|
||||||
typename detail::vformat_args<Char>::type args) -> OutputIt {
|
basic_format_args<buffered_context<Char>> args) -> OutputIt {
|
||||||
auto&& buf = detail::get_buffer<Char>(out);
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
detail::vformat_to(buf, detail::to_string_view(format_str), args);
|
detail::vformat_to(buf, detail::to_string_view(fmt), args);
|
||||||
return detail::get_iterator(buf, out);
|
return detail::get_iterator(buf, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,42 +233,37 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
|
|||||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
template <typename S, typename OutputIt, typename... Args,
|
||||||
typename Char = detail::format_string_char_t<S>,
|
typename Char = detail::format_string_char_t<S>,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
detail::is_locale<Locale>::value&&
|
detail::is_exotic_char<Char>::value)>
|
||||||
detail::is_exotic_char<Char>::value)>
|
inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
|
||||||
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str,
|
basic_format_args<buffered_context<Char>> args)
|
||||||
typename detail::vformat_args<Char>::type args)
|
|
||||||
-> OutputIt {
|
-> OutputIt {
|
||||||
auto&& buf = detail::get_buffer<Char>(out);
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
vformat_to(buf, detail::to_string_view(format_str), args,
|
vformat_to(buf, detail::to_string_view(fmt), args, loc);
|
||||||
detail::locale_ref(loc));
|
|
||||||
return detail::get_iterator(buf, out);
|
return detail::get_iterator(buf, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Locale, typename S, typename... T,
|
template <typename OutputIt, typename S, typename... T,
|
||||||
typename Char = detail::format_string_char_t<S>,
|
typename Char = detail::format_string_char_t<S>,
|
||||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
|
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
|
||||||
detail::is_locale<Locale>::value &&
|
|
||||||
detail::is_exotic_char<Char>::value>
|
detail::is_exotic_char<Char>::value>
|
||||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
|
||||||
T&&... args) ->
|
-> typename std::enable_if<enable, OutputIt>::type {
|
||||||
typename std::enable_if<enable, OutputIt>::type {
|
return vformat_to(out, loc, detail::to_string_view(fmt),
|
||||||
return vformat_to(out, loc, detail::to_string_view(format_str),
|
|
||||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Char, typename... Args,
|
template <typename OutputIt, typename Char, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
detail::is_exotic_char<Char>::value)>
|
detail::is_exotic_char<Char>::value)>
|
||||||
inline auto vformat_to_n(OutputIt out, size_t n,
|
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
|
||||||
basic_string_view<Char> format_str,
|
basic_format_args<buffered_context<Char>> args)
|
||||||
typename detail::vformat_args<Char>::type args)
|
|
||||||
-> format_to_n_result<OutputIt> {
|
-> format_to_n_result<OutputIt> {
|
||||||
using traits = detail::fixed_buffer_traits;
|
using traits = detail::fixed_buffer_traits;
|
||||||
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
||||||
detail::vformat_to(buf, format_str, args);
|
detail::vformat_to(buf, fmt, args);
|
||||||
return {buf.out(), buf.count()};
|
return {buf.out(), buf.count()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,29 +317,33 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
|
|||||||
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
|
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
|
||||||
-> std::wstring {
|
-> std::wstring {
|
||||||
auto buf = wmemory_buffer();
|
auto buf = wmemory_buffer();
|
||||||
detail::vformat_to(buf, ts, fmt, args);
|
detail::vformat_to(buf, ts, fmt, args);
|
||||||
return fmt::to_string(buf);
|
return {buf.data(), buf.size()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
|
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
|
||||||
-> std::wstring {
|
-> std::wstring {
|
||||||
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
|
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
|
||||||
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
|
auto buffer = basic_memory_buffer<wchar_t>();
|
||||||
wformat_string<T...> fmt, const T&... args) {
|
detail::vformat_to(buffer, fmt, args);
|
||||||
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
|
detail::write_buffer(os, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
|
void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
|
||||||
const T&... args) {
|
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
|
||||||
return print(stdout, ts, fmt, args...);
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
|
||||||
|
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts `value` to `std::wstring` using the default format for type `T`.
|
/// Converts `value` to `std::wstring` using the default format for type `T`.
|
||||||
|
|||||||
22
src/fmt.cc
22
src/fmt.cc
@ -1,5 +1,13 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#define FMT_MODULE
|
||||||
|
|
||||||
|
#ifdef _MSVC_LANG
|
||||||
|
# define FMT_CPLUSPLUS _MSVC_LANG
|
||||||
|
#else
|
||||||
|
# define FMT_CPLUSPLUS __cplusplus
|
||||||
|
#endif
|
||||||
|
|
||||||
// Put all implementation-provided headers into the global module fragment
|
// Put all implementation-provided headers into the global module fragment
|
||||||
// to prevent attachment to this module.
|
// to prevent attachment to this module.
|
||||||
#ifndef FMT_IMPORT_STD
|
#ifndef FMT_IMPORT_STD
|
||||||
@ -15,7 +23,9 @@ module;
|
|||||||
# include <cstring>
|
# include <cstring>
|
||||||
# include <ctime>
|
# include <ctime>
|
||||||
# include <exception>
|
# include <exception>
|
||||||
# include <expected>
|
# if FMT_CPLUSPLUS > 202002L
|
||||||
|
# include <expected>
|
||||||
|
# endif
|
||||||
# include <filesystem>
|
# include <filesystem>
|
||||||
# include <fstream>
|
# include <fstream>
|
||||||
# include <functional>
|
# include <functional>
|
||||||
@ -40,6 +50,8 @@ module;
|
|||||||
# include <limits.h>
|
# include <limits.h>
|
||||||
# include <stdint.h>
|
# include <stdint.h>
|
||||||
# include <stdio.h>
|
# include <stdio.h>
|
||||||
|
# include <stdlib.h>
|
||||||
|
# include <string.h>
|
||||||
# include <time.h>
|
# include <time.h>
|
||||||
#endif
|
#endif
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
@ -127,9 +139,17 @@ extern "C++" {
|
|||||||
module :private;
|
module :private;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||||
|
extern "C++" {
|
||||||
|
#endif
|
||||||
|
|
||||||
#if FMT_HAS_INCLUDE("format.cc")
|
#if FMT_HAS_INCLUDE("format.cc")
|
||||||
# include "format.cc"
|
# include "format.cc"
|
||||||
#endif
|
#endif
|
||||||
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
|
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
|
||||||
# include "os.cc"
|
# include "os.cc"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@ -8,6 +8,12 @@
|
|||||||
#include "fmt/format-inl.h"
|
#include "fmt/format-inl.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
#if FMT_USE_LOCALE
|
||||||
|
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
|
||||||
|
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template FMT_API auto dragonbox::to_decimal(float x) noexcept
|
template FMT_API auto dragonbox::to_decimal(float x) noexcept
|
||||||
@ -15,28 +21,22 @@ template FMT_API auto dragonbox::to_decimal(float x) noexcept
|
|||||||
template FMT_API auto dragonbox::to_decimal(double x) noexcept
|
template FMT_API auto dragonbox::to_decimal(double x) noexcept
|
||||||
-> dragonbox::decimal_fp<double>;
|
-> dragonbox::decimal_fp<double>;
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
|
||||||
template FMT_API locale_ref::locale_ref(const std::locale& loc);
|
|
||||||
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Explicit instantiations for char.
|
// Explicit instantiations for char.
|
||||||
|
|
||||||
template FMT_API auto thousands_sep_impl(locale_ref)
|
template FMT_API auto thousands_sep_impl(locale_ref)
|
||||||
-> thousands_sep_result<char>;
|
-> thousands_sep_result<char>;
|
||||||
template FMT_API auto decimal_point_impl(locale_ref) -> char;
|
template FMT_API auto decimal_point_impl(locale_ref) -> char;
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
template FMT_API void buffer<char>::append(const char*, const char*);
|
template FMT_API void buffer<char>::append(const char*, const char*);
|
||||||
|
|
||||||
template FMT_API void vformat_to(buffer<char>&, string_view,
|
|
||||||
typename vformat_args<>::type, locale_ref);
|
|
||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
// Explicit instantiations for wchar_t.
|
||||||
|
|
||||||
template FMT_API auto thousands_sep_impl(locale_ref)
|
template FMT_API auto thousands_sep_impl(locale_ref)
|
||||||
-> thousands_sep_result<wchar_t>;
|
-> thousands_sep_result<wchar_t>;
|
||||||
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
|
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
|
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
37
src/os.cc
37
src/os.cc
@ -66,14 +66,14 @@ using rwresult = int;
|
|||||||
|
|
||||||
// On Windows the count argument to read and write is unsigned, so convert
|
// On Windows the count argument to read and write is unsigned, so convert
|
||||||
// it from size_t preventing integer overflow.
|
// it from size_t preventing integer overflow.
|
||||||
inline unsigned convert_rwcount(std::size_t count) {
|
inline unsigned convert_rwcount(size_t count) {
|
||||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||||
}
|
}
|
||||||
#elif FMT_USE_FCNTL
|
#elif FMT_USE_FCNTL
|
||||||
// Return type of read and write functions.
|
// Return type of read and write functions.
|
||||||
using rwresult = ssize_t;
|
using rwresult = ssize_t;
|
||||||
|
|
||||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
inline auto convert_rwcount(size_t count) -> size_t { return count; }
|
||||||
#endif
|
#endif
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void report_windows_error(int error_code, const char* message) noexcept {
|
void report_windows_error(int error_code, const char* message) noexcept {
|
||||||
report_error(detail::format_windows_error, error_code, message);
|
do_report_error(detail::format_windows_error, error_code, message);
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ void buffered_file::close() {
|
|||||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||||
}
|
}
|
||||||
|
|
||||||
int buffered_file::descriptor() const {
|
auto buffered_file::descriptor() const -> int {
|
||||||
#ifdef FMT_HAS_SYSTEM
|
#ifdef FMT_HAS_SYSTEM
|
||||||
// fileno is a macro on OpenBSD.
|
// fileno is a macro on OpenBSD.
|
||||||
# ifdef fileno
|
# ifdef fileno
|
||||||
@ -240,7 +240,7 @@ void file::close() {
|
|||||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||||
}
|
}
|
||||||
|
|
||||||
long long file::size() const {
|
auto file::size() const -> long long {
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||||
@ -251,7 +251,7 @@ long long file::size() const {
|
|||||||
if (size_lower == INVALID_FILE_SIZE) {
|
if (size_lower == INVALID_FILE_SIZE) {
|
||||||
DWORD error = GetLastError();
|
DWORD error = GetLastError();
|
||||||
if (error != NO_ERROR)
|
if (error != NO_ERROR)
|
||||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
FMT_THROW(windows_error(error, "cannot get file size"));
|
||||||
}
|
}
|
||||||
unsigned long long long_size = size_upper;
|
unsigned long long long_size = size_upper;
|
||||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||||
@ -266,7 +266,7 @@ long long file::size() const {
|
|||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t file::read(void* buffer, std::size_t count) {
|
auto file::read(void* buffer, size_t count) -> size_t {
|
||||||
rwresult result = 0;
|
rwresult result = 0;
|
||||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
@ -274,7 +274,7 @@ std::size_t file::read(void* buffer, std::size_t count) {
|
|||||||
return detail::to_unsigned(result);
|
return detail::to_unsigned(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
auto file::write(const void* buffer, size_t count) -> size_t {
|
||||||
rwresult result = 0;
|
rwresult result = 0;
|
||||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
@ -282,7 +282,7 @@ std::size_t file::write(const void* buffer, std::size_t count) {
|
|||||||
return detail::to_unsigned(result);
|
return detail::to_unsigned(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
file file::dup(int fd) {
|
auto file::dup(int fd) -> file {
|
||||||
// Don't retry as dup doesn't return EINTR.
|
// Don't retry as dup doesn't return EINTR.
|
||||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||||
@ -308,7 +308,7 @@ void file::dup2(int fd, std::error_code& ec) noexcept {
|
|||||||
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
buffered_file file::fdopen(const char* mode) {
|
auto file::fdopen(const char* mode) -> buffered_file {
|
||||||
// Don't retry as fdopen doesn't return EINTR.
|
// Don't retry as fdopen doesn't return EINTR.
|
||||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||||
FILE* f = ::fdopen(fd_, mode);
|
FILE* f = ::fdopen(fd_, mode);
|
||||||
@ -355,7 +355,7 @@ pipe::pipe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# if !defined(__MSDOS__)
|
# if !defined(__MSDOS__)
|
||||||
long getpagesize() {
|
auto getpagesize() -> long {
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
SYSTEM_INFO si;
|
SYSTEM_INFO si;
|
||||||
GetSystemInfo(&si);
|
GetSystemInfo(&si);
|
||||||
@ -374,30 +374,25 @@ long getpagesize() {
|
|||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
namespace detail {
|
void ostream::grow(buffer<char>& buf, size_t) {
|
||||||
|
if (buf.size() == buf.capacity()) static_cast<ostream&>(buf).flush();
|
||||||
void file_buffer::grow(buffer<char>& buf, size_t) {
|
|
||||||
if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file_buffer::file_buffer(cstring_view path, const ostream_params& params)
|
ostream::ostream(cstring_view path, const detail::ostream_params& params)
|
||||||
: buffer<char>(grow), file_(path, params.oflag) {
|
: buffer<char>(grow), file_(path, params.oflag) {
|
||||||
set(new char[params.buffer_size], params.buffer_size);
|
set(new char[params.buffer_size], params.buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
file_buffer::file_buffer(file_buffer&& other) noexcept
|
ostream::ostream(ostream&& other) noexcept
|
||||||
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
|
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
|
||||||
file_(std::move(other.file_)) {
|
file_(std::move(other.file_)) {
|
||||||
other.clear();
|
other.clear();
|
||||||
other.set(nullptr, 0);
|
other.set(nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
file_buffer::~file_buffer() {
|
ostream::~ostream() {
|
||||||
flush();
|
flush();
|
||||||
delete[] data();
|
delete[] data();
|
||||||
}
|
}
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
ostream::~ostream() = default;
|
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
7.1.2
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
cc_library(
|
|
||||||
name = "fmt",
|
|
||||||
srcs = [
|
|
||||||
#"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940)
|
|
||||||
"src/format.cc",
|
|
||||||
"src/os.cc",
|
|
||||||
],
|
|
||||||
hdrs = glob([
|
|
||||||
"include/fmt/*.h",
|
|
||||||
]),
|
|
||||||
copts = select({
|
|
||||||
"@platforms//os:windows": ["-utf-8"],
|
|
||||||
"//conditions:default": [],
|
|
||||||
}),
|
|
||||||
includes = [
|
|
||||||
"include",
|
|
||||||
],
|
|
||||||
strip_include_prefix = "include",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
module(
|
|
||||||
name = "fmt",
|
|
||||||
compatibility_level = 10,
|
|
||||||
)
|
|
||||||
|
|
||||||
bazel_dep(name = "platforms", version = "0.0.10")
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# Bazel support
|
|
||||||
|
|
||||||
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`,
|
|
||||||
`MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project.
|
|
||||||
This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
|
|
||||||
|
|
||||||
## Using {fmt} as a dependency
|
|
||||||
|
|
||||||
### Using Bzlmod
|
|
||||||
|
|
||||||
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}.
|
|
||||||
|
|
||||||
For instance, to use {fmt} add to your `MODULE.bazel` file:
|
|
||||||
|
|
||||||
```
|
|
||||||
bazel_dep(name = "fmt", version = "10.2.1")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Live at head
|
|
||||||
|
|
||||||
For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.:
|
|
||||||
|
|
||||||
```
|
|
||||||
local_path_override(
|
|
||||||
module_name = "fmt",
|
|
||||||
path = "../third_party/fmt",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
# WORKSPACE marker file needed by Bazel
|
|
||||||
|
|
||||||
43
support/check-commits
Executable file
43
support/check-commits
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Compile source on a range of commits
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
check-commits <start> <source>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import docopt, os, sys, tempfile
|
||||||
|
from subprocess import check_call, check_output, run
|
||||||
|
|
||||||
|
args = docopt.docopt(__doc__)
|
||||||
|
start = args.get('<start>')
|
||||||
|
source = args.get('<source>')
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as work_dir:
|
||||||
|
check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'],
|
||||||
|
cwd=work_dir)
|
||||||
|
repo_dir = os.path.join(work_dir, 'fmt')
|
||||||
|
commits = check_output(
|
||||||
|
['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit',
|
||||||
|
'--', 'include', 'src'],
|
||||||
|
text=True, cwd=repo_dir).rstrip().split('\n')
|
||||||
|
commits.reverse()
|
||||||
|
print('Time\tCommit')
|
||||||
|
for commit in commits:
|
||||||
|
check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit],
|
||||||
|
cwd=repo_dir)
|
||||||
|
returncode = run(
|
||||||
|
['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include',
|
||||||
|
'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode
|
||||||
|
if returncode != 0:
|
||||||
|
continue
|
||||||
|
times = []
|
||||||
|
for i in range(5):
|
||||||
|
output = check_output([os.path.join(repo_dir, 'a.out')], text=True)
|
||||||
|
times.append(float(output))
|
||||||
|
message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit],
|
||||||
|
cwd=repo_dir, text=True)
|
||||||
|
print(f'{min(times)}\t{commit} {message[:40]}')
|
||||||
|
sys.stdout.flush()
|
||||||
@ -2,6 +2,10 @@
|
|||||||
# A script to invoke mkdocs with the correct environment.
|
# A script to invoke mkdocs with the correct environment.
|
||||||
# Additionally supports deploying via mike:
|
# Additionally supports deploying via mike:
|
||||||
# ./mkdocs deploy [mike-deploy-options]
|
# ./mkdocs deploy [mike-deploy-options]
|
||||||
|
# For example:
|
||||||
|
# ./mkdocs deploy <version>
|
||||||
|
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
|
||||||
|
# <version> there.
|
||||||
|
|
||||||
import errno, os, shutil, sys
|
import errno, os, shutil, sys
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
@ -15,15 +19,36 @@ path = env.get('PYTHONPATH')
|
|||||||
env['PYTHONPATH'] = \
|
env['PYTHONPATH'] = \
|
||||||
(path + ':' if path else '') + os.path.join(support_dir, 'python')
|
(path + ':' if path else '') + os.path.join(support_dir, 'python')
|
||||||
|
|
||||||
|
redirect_page = \
|
||||||
|
'''<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Redirecting</title>
|
||||||
|
<noscript>
|
||||||
|
<meta http-equiv="refresh" content="1; url=11.0/" />
|
||||||
|
</noscript>
|
||||||
|
<script>
|
||||||
|
window.location.replace(
|
||||||
|
"api/" + window.location.search + window.location.hash
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Redirecting to <a href="api/">api</a>...
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
|
||||||
config_path = os.path.join(support_dir, 'mkdocs.yml')
|
config_path = os.path.join(support_dir, 'mkdocs.yml')
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
if len(args) > 0:
|
if len(args) > 0:
|
||||||
command = args[0]
|
command = args[0]
|
||||||
if command == 'deploy':
|
if command == 'deploy' or command == 'set-default':
|
||||||
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
|
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
|
||||||
site_repo = git_url + 'fmtlib/fmt.dev.git'
|
site_repo = git_url + 'fmtlib/fmt.dev.git'
|
||||||
|
|
||||||
site_dir = os. path.join(build_dir, 'fmt.dev')
|
site_dir = os.path.join(build_dir, 'fmt.dev')
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(site_dir)
|
shutil.rmtree(site_dir)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@ -37,8 +62,25 @@ if len(args) > 0:
|
|||||||
config_build_path = os.path.join(build_dir, 'mkdocs.yml')
|
config_build_path = os.path.join(build_dir, 'mkdocs.yml')
|
||||||
shutil.copyfile(config_path, config_build_path)
|
shutil.copyfile(config_path, config_build_path)
|
||||||
|
|
||||||
sys.exit(call(['mike'] + args + ['--config-file', config_build_path,
|
version = args[1]
|
||||||
'--branch', 'master'], cwd=site_dir, env=env))
|
ret = call(['mike'] + args + ['--config-file', config_build_path,
|
||||||
|
'--branch', 'master'], cwd=site_dir, env=env)
|
||||||
|
if ret != 0 or version == 'dev':
|
||||||
|
sys.exit(ret)
|
||||||
|
current_doc_path = os.path.join(site_dir, version)
|
||||||
|
# mike stages files added by deploy for deletion for unclear reason,
|
||||||
|
# undo it.
|
||||||
|
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
|
||||||
|
if False:
|
||||||
|
os.makedirs(current_doc_path, exist_ok=True)
|
||||||
|
redirect_page_path = os.path.join(current_doc_path, 'api.html')
|
||||||
|
with open(redirect_page_path, "w") as file:
|
||||||
|
file.write(redirect_page)
|
||||||
|
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
|
||||||
|
if ret != 0:
|
||||||
|
sys.exit(ret)
|
||||||
|
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
|
||||||
|
sys.exit(ret)
|
||||||
elif not command.startswith('-'):
|
elif not command.startswith('-'):
|
||||||
args += ['-f', config_path]
|
args += ['-f', config_path]
|
||||||
sys.exit(call(['mkdocs'] + args, env=env))
|
sys.exit(call(['mkdocs'] + args, env=env))
|
||||||
|
|||||||
@ -1,317 +1,354 @@
|
|||||||
# A basic mkdocstrings handler for {fmt}.
|
# A basic mkdocstrings handler for {fmt}.
|
||||||
# Copyright (c) 2012 - present, Victor Zverovich
|
# Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
# https://github.com/fmtlib/fmt/blob/master/LICENSE
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import xml.etree.ElementTree as ElementTree
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
|
||||||
from typing import Any, List, Mapping, Optional
|
from typing import Any, List, Mapping, Optional
|
||||||
from subprocess import CalledProcessError, PIPE, Popen, STDOUT
|
|
||||||
import xml.etree.ElementTree as et
|
|
||||||
|
|
||||||
from mkdocstrings.handlers.base import BaseHandler
|
from mkdocstrings.handlers.base import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
class Definition:
|
class Definition:
|
||||||
'''A definition extracted by Doxygen.'''
|
"""A definition extracted by Doxygen."""
|
||||||
def __init__(self, name: str, kind: Optional[str] = None,
|
|
||||||
node: Optional[et.Element] = None,
|
def __init__(self, name: str, kind: Optional[str] = None,
|
||||||
is_member: bool = False):
|
node: Optional[ElementTree.Element] = None,
|
||||||
self.name = name
|
is_member: bool = False):
|
||||||
self.kind = kind if kind is not None else node.get('kind')
|
self.name = name
|
||||||
self.id = name if not is_member else None
|
self.kind = kind if kind is not None else node.get('kind')
|
||||||
self.params = None
|
self.desc = None
|
||||||
self.members = None
|
self.id = name if not is_member else None
|
||||||
|
self.members = None
|
||||||
|
self.params = None
|
||||||
|
self.template_params = None
|
||||||
|
self.trailing_return_type = None
|
||||||
|
self.type = None
|
||||||
|
|
||||||
|
|
||||||
# A map from Doxygen to HTML tags.
|
# A map from Doxygen to HTML tags.
|
||||||
tag_map = {
|
tag_map = {
|
||||||
'bold': 'b',
|
'bold': 'b',
|
||||||
'emphasis': 'em',
|
'emphasis': 'em',
|
||||||
'computeroutput': 'code',
|
'computeroutput': 'code',
|
||||||
'para': 'p',
|
'para': 'p',
|
||||||
'programlisting': 'pre',
|
'itemizedlist': 'ul',
|
||||||
'verbatim': 'pre'
|
'listitem': 'li'
|
||||||
}
|
}
|
||||||
|
|
||||||
# A map from Doxygen tags to text.
|
# A map from Doxygen tags to text.
|
||||||
tag_text_map = {
|
tag_text_map = {
|
||||||
'codeline': '',
|
'codeline': '',
|
||||||
'highlight': '',
|
'highlight': '',
|
||||||
'sp': ' '
|
'sp': ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def escape_html(s: str) -> str:
|
def escape_html(s: str) -> str:
|
||||||
return s.replace("<", "<")
|
return s.replace("<", "<")
|
||||||
|
|
||||||
def doxyxml2html(nodes: List[et.Element]):
|
|
||||||
out = ''
|
|
||||||
for n in nodes:
|
|
||||||
tag = tag_map.get(n.tag)
|
|
||||||
if not tag:
|
|
||||||
out += tag_text_map[n.tag]
|
|
||||||
out += '<' + tag + '>' if tag else ''
|
|
||||||
out += '<code class="language-cpp">' if tag == 'pre' else ''
|
|
||||||
if n.text:
|
|
||||||
out += escape_html(n.text)
|
|
||||||
out += doxyxml2html(n)
|
|
||||||
out += '</code>' if tag == 'pre' else ''
|
|
||||||
out += '</' + tag + '>' if tag else ''
|
|
||||||
if n.tail:
|
|
||||||
out += n.tail
|
|
||||||
return out
|
|
||||||
|
|
||||||
def convert_template_params(node: et.Element) -> Optional[List[Definition]]:
|
# Converts a node from doxygen to HTML format.
|
||||||
templateparamlist = node.find('templateparamlist')
|
def convert_node(node: ElementTree.Element, tag: str, attrs: dict = {}):
|
||||||
if templateparamlist is None:
|
out = '<' + tag
|
||||||
return None
|
for key, value in attrs.items():
|
||||||
params = []
|
out += ' ' + key + '="' + value + '"'
|
||||||
for param_node in templateparamlist.findall('param'):
|
out += '>'
|
||||||
name = param_node.find('declname')
|
if node.text:
|
||||||
param = Definition(name.text if name is not None else '', 'param')
|
out += escape_html(node.text)
|
||||||
param.type = param_node.find('type').text
|
out += doxyxml2html(list(node))
|
||||||
params.append(param)
|
out += '</' + tag + '>'
|
||||||
return params
|
if node.tail:
|
||||||
|
out += node.tail
|
||||||
|
return out
|
||||||
|
|
||||||
def get_description(node: et.Element) -> List[et.Element]:
|
|
||||||
return node.findall('briefdescription/para') + \
|
|
||||||
node.findall('detaileddescription/para')
|
|
||||||
|
|
||||||
def normalize_type(type: str) -> str:
|
def doxyxml2html(nodes: List[ElementTree.Element]):
|
||||||
type = type.replace('< ', '<').replace(' >', '>')
|
out = ''
|
||||||
return type.replace(' &', '&').replace(' *', '*')
|
for n in nodes:
|
||||||
|
tag = tag_map.get(n.tag)
|
||||||
|
if tag:
|
||||||
|
out += convert_node(n, tag)
|
||||||
|
continue
|
||||||
|
if n.tag == 'programlisting' or n.tag == 'verbatim':
|
||||||
|
out += '<pre>'
|
||||||
|
out += convert_node(n, 'code', {'class': 'language-cpp'})
|
||||||
|
out += '</pre>'
|
||||||
|
continue
|
||||||
|
if n.tag == 'ulink':
|
||||||
|
out += convert_node(n, 'a', {'href': n.attrib['url']})
|
||||||
|
continue
|
||||||
|
out += tag_text_map[n.tag]
|
||||||
|
return out
|
||||||
|
|
||||||
def convert_type(type: et.Element) -> str:
|
|
||||||
if type is None:
|
|
||||||
return None
|
|
||||||
result = type.text if type.text else ''
|
|
||||||
for ref in type:
|
|
||||||
result += ref.text
|
|
||||||
if ref.tail:
|
|
||||||
result += ref.tail
|
|
||||||
result += type.tail.strip()
|
|
||||||
return normalize_type(result)
|
|
||||||
|
|
||||||
def convert_params(func: et.Element) -> Definition:
|
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
|
||||||
params = []
|
template_param_list = node.find('templateparamlist')
|
||||||
for p in func.findall('param'):
|
if template_param_list is None:
|
||||||
d = Definition(p.find('declname').text, 'param')
|
return None
|
||||||
d.type = convert_type(p.find('type'))
|
params = []
|
||||||
params.append(d)
|
for param_node in template_param_list.findall('param'):
|
||||||
return params
|
name = param_node.find('declname')
|
||||||
|
param = Definition(name.text if name is not None else '', 'param')
|
||||||
|
param.type = param_node.find('type').text
|
||||||
|
params.append(param)
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
|
||||||
|
return node.findall('briefdescription/para') + \
|
||||||
|
node.findall('detaileddescription/para')
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_type(type_: str) -> str:
|
||||||
|
type_ = type_.replace('< ', '<').replace(' >', '>')
|
||||||
|
return type_.replace(' &', '&').replace(' *', '*')
|
||||||
|
|
||||||
|
|
||||||
|
def convert_type(type_: ElementTree.Element) -> Optional[str]:
|
||||||
|
if type_ is None:
|
||||||
|
return None
|
||||||
|
result = type_.text if type_.text else ''
|
||||||
|
for ref in type_:
|
||||||
|
result += ref.text
|
||||||
|
if ref.tail:
|
||||||
|
result += ref.tail
|
||||||
|
result += type_.tail.strip()
|
||||||
|
return normalize_type(result)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_params(func: ElementTree.Element) -> List[Definition]:
|
||||||
|
params = []
|
||||||
|
for p in func.findall('param'):
|
||||||
|
d = Definition(p.find('declname').text, 'param')
|
||||||
|
d.type = convert_type(p.find('type'))
|
||||||
|
params.append(d)
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
|
||||||
|
d.trailing_return_type = None
|
||||||
|
if d.type == 'auto' or d.type == 'constexpr auto':
|
||||||
|
parts = node.find('argsstring').text.split(' -> ')
|
||||||
|
if len(parts) > 1:
|
||||||
|
d.trailing_return_type = normalize_type(parts[1])
|
||||||
|
|
||||||
def convert_return_type(d: Definition, node: et.Element) -> None:
|
|
||||||
d.trailing_return_type = None
|
|
||||||
if d.type == 'auto' or d.type == 'constexpr auto':
|
|
||||||
parts = node.find('argsstring').text.split(' -> ')
|
|
||||||
if len(parts) > 1:
|
|
||||||
d.trailing_return_type = normalize_type(parts[1])
|
|
||||||
|
|
||||||
def render_param(param: Definition) -> str:
|
def render_param(param: Definition) -> str:
|
||||||
return param.type + (f' {param.name}' if len(param.name) > 0 else '')
|
return param.type + (f' {param.name}' if len(param.name) > 0 else '')
|
||||||
|
|
||||||
def render_decl(d: Definition) -> None:
|
|
||||||
text = ''
|
|
||||||
if d.id is not None:
|
|
||||||
text += f'<a id="{d.id}">\n'
|
|
||||||
text += '<pre><code class="language-cpp decl">'
|
|
||||||
|
|
||||||
text += '<div>'
|
def render_decl(d: Definition) -> str:
|
||||||
if d.template_params is not None:
|
text = ''
|
||||||
text += 'template <'
|
|
||||||
text += ', '.join([render_param(p) for p in d.template_params])
|
|
||||||
text += '>\n'
|
|
||||||
text += '</div>'
|
|
||||||
|
|
||||||
text += '<div>'
|
|
||||||
end = ';'
|
|
||||||
if d.kind == 'function' or d.kind == 'variable':
|
|
||||||
text += d.type + ' ' if len(d.type) > 0 else ''
|
|
||||||
elif d.kind == 'typedef':
|
|
||||||
text += 'using '
|
|
||||||
elif d.kind == 'define':
|
|
||||||
end = ''
|
|
||||||
else:
|
|
||||||
text += d.kind + ' '
|
|
||||||
text += d.name
|
|
||||||
|
|
||||||
if d.params is not None:
|
|
||||||
params = ', '.join([
|
|
||||||
(p.type + ' ' if p.type else '') + p.name for p in d.params])
|
|
||||||
text += '(' + escape_html(params) + ')'
|
|
||||||
if d.trailing_return_type:
|
|
||||||
text += ' -⁠> ' + escape_html(d.trailing_return_type)
|
|
||||||
elif d.kind == 'typedef':
|
|
||||||
text += ' = ' + escape_html(d.type)
|
|
||||||
|
|
||||||
text += end
|
|
||||||
text += '</div>'
|
|
||||||
text += '</code></pre>\n'
|
|
||||||
if d.id is not None:
|
|
||||||
text += f'</a>\n'
|
|
||||||
return text
|
|
||||||
|
|
||||||
class CxxHandler(BaseHandler):
|
|
||||||
def __init__(self, **kwargs: Any) -> None:
|
|
||||||
super().__init__(handler='cxx', **kwargs)
|
|
||||||
|
|
||||||
headers = [
|
|
||||||
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
|
|
||||||
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Run doxygen.
|
|
||||||
cmd = ['doxygen', '-']
|
|
||||||
support_dir = Path(__file__).parents[3]
|
|
||||||
top_dir = os.path.dirname(support_dir)
|
|
||||||
include_dir = os.path.join(top_dir, 'include', 'fmt')
|
|
||||||
self._ns2doxyxml = {}
|
|
||||||
build_dir = os.path.join(top_dir, 'build')
|
|
||||||
os.makedirs(build_dir, exist_ok=True)
|
|
||||||
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
|
|
||||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
|
||||||
_, _ = p.communicate(input=r'''
|
|
||||||
PROJECT_NAME = fmt
|
|
||||||
GENERATE_XML = YES
|
|
||||||
GENERATE_LATEX = NO
|
|
||||||
GENERATE_HTML = NO
|
|
||||||
INPUT = {0}
|
|
||||||
XML_OUTPUT = {1}
|
|
||||||
QUIET = YES
|
|
||||||
AUTOLINK_SUPPORT = NO
|
|
||||||
MACRO_EXPANSION = YES
|
|
||||||
PREDEFINED = _WIN32=1 \
|
|
||||||
__linux__=1 \
|
|
||||||
FMT_ENABLE_IF(...)= \
|
|
||||||
FMT_USE_USER_DEFINED_LITERALS=1 \
|
|
||||||
FMT_USE_ALIAS_TEMPLATES=1 \
|
|
||||||
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
|
|
||||||
FMT_API= \
|
|
||||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
|
||||||
"FMT_END_NAMESPACE=}}" \
|
|
||||||
"FMT_DOC=1"
|
|
||||||
'''.format(
|
|
||||||
' '.join([os.path.join(include_dir, h) for h in headers]),
|
|
||||||
self._doxyxml_dir).encode('utf-8'))
|
|
||||||
if p.returncode != 0:
|
|
||||||
raise CalledProcessError(p.returncode, cmd)
|
|
||||||
|
|
||||||
# Merge all file-level XMLs into one to simplify search.
|
|
||||||
self._file_doxyxml = None
|
|
||||||
for h in headers:
|
|
||||||
filename = h.replace(".h", "_8h.xml")
|
|
||||||
with open(os.path.join(self._doxyxml_dir, filename)) as f:
|
|
||||||
doxyxml = et.parse(f)
|
|
||||||
if self._file_doxyxml is None:
|
|
||||||
self._file_doxyxml = doxyxml
|
|
||||||
continue
|
|
||||||
root = self._file_doxyxml.getroot()
|
|
||||||
for node in doxyxml.getroot():
|
|
||||||
root.append(node)
|
|
||||||
|
|
||||||
def collect_compound(self, identifier: str,
|
|
||||||
cls: List[et.Element]) -> Definition:
|
|
||||||
'''Collect a compound definition such as a struct.'''
|
|
||||||
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
|
|
||||||
with open(path) as f:
|
|
||||||
xml = et.parse(f)
|
|
||||||
node = xml.find('compounddef')
|
|
||||||
d = Definition(identifier, node=node)
|
|
||||||
d.template_params = convert_template_params(node)
|
|
||||||
d.desc = get_description(node)
|
|
||||||
d.members = []
|
|
||||||
for m in node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
|
|
||||||
node.findall('sectiondef[@kind="public-func"]/memberdef'):
|
|
||||||
name = m.find('name').text
|
|
||||||
# Doxygen incorrectly classifies members of private unnamed unions as
|
|
||||||
# public members of the containing class.
|
|
||||||
if name.endswith('_'):
|
|
||||||
continue
|
|
||||||
desc = get_description(m)
|
|
||||||
if len(desc) == 0:
|
|
||||||
continue
|
|
||||||
kind = m.get('kind')
|
|
||||||
member = Definition(name if name else '', kind=kind, is_member=True)
|
|
||||||
type = m.find('type').text
|
|
||||||
member.type = type if type else ''
|
|
||||||
if kind == 'function':
|
|
||||||
member.params = convert_params(m)
|
|
||||||
convert_return_type(member, m)
|
|
||||||
member.template_params = None
|
|
||||||
member.desc = desc
|
|
||||||
d.members.append(member)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def collect(self, identifier: str, config: Mapping[str, Any]) -> Definition:
|
|
||||||
qual_name = 'fmt::' + identifier
|
|
||||||
|
|
||||||
param_str = None
|
|
||||||
paren = qual_name.find('(')
|
|
||||||
if paren > 0:
|
|
||||||
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
|
|
||||||
|
|
||||||
colons = qual_name.rfind('::')
|
|
||||||
namespace, name = qual_name[:colons], qual_name[colons + 2:]
|
|
||||||
|
|
||||||
# Load XML.
|
|
||||||
doxyxml = self._ns2doxyxml.get(namespace)
|
|
||||||
if doxyxml is None:
|
|
||||||
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
|
|
||||||
with open(os.path.join(self._doxyxml_dir, path)) as f:
|
|
||||||
doxyxml = et.parse(f)
|
|
||||||
self._ns2doxyxml[namespace] = doxyxml
|
|
||||||
|
|
||||||
nodes = doxyxml.findall(
|
|
||||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
|
||||||
if len(nodes) == 0:
|
|
||||||
nodes = self._file_doxyxml.findall(
|
|
||||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
|
||||||
candidates = []
|
|
||||||
for node in nodes:
|
|
||||||
# Process a function or a typedef.
|
|
||||||
params = None
|
|
||||||
d = Definition(name, node=node)
|
|
||||||
if d.kind == 'function':
|
|
||||||
params = convert_params(node)
|
|
||||||
node_param_str = ', '.join([p.type for p in params])
|
|
||||||
if param_str and param_str != node_param_str:
|
|
||||||
candidates.append(f'{name}({node_param_str})')
|
|
||||||
continue
|
|
||||||
elif d.kind == 'define':
|
|
||||||
params = []
|
|
||||||
for p in node.findall('param'):
|
|
||||||
param = Definition(p.find('defname').text, kind='param')
|
|
||||||
param.type = None
|
|
||||||
params.append(param)
|
|
||||||
d.type = convert_type(node.find('type'))
|
|
||||||
d.template_params = convert_template_params(node)
|
|
||||||
d.params = params
|
|
||||||
convert_return_type(d, node)
|
|
||||||
d.desc = get_description(node)
|
|
||||||
return d
|
|
||||||
|
|
||||||
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
|
|
||||||
if not cls:
|
|
||||||
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
|
|
||||||
return self.collect_compound(identifier, cls)
|
|
||||||
|
|
||||||
def render(self, d: Definition, config: dict) -> str:
|
|
||||||
if d.id is not None:
|
if d.id is not None:
|
||||||
self.do_heading('', 0, id=d.id)
|
text += f'<a id="{d.id}">\n'
|
||||||
text = '<div class="docblock">\n'
|
text += '<pre><code class="language-cpp decl">'
|
||||||
text += render_decl(d)
|
|
||||||
text += '<div class="docblock-desc">\n'
|
text += '<div>'
|
||||||
text += doxyxml2html(d.desc)
|
if d.template_params is not None:
|
||||||
if d.members is not None:
|
text += 'template <'
|
||||||
for m in d.members:
|
text += ', '.join([render_param(p) for p in d.template_params])
|
||||||
text += self.render(m, config)
|
text += '>\n'
|
||||||
text += '</div>\n'
|
text += '</div>'
|
||||||
text += '</div>\n'
|
|
||||||
|
text += '<div>'
|
||||||
|
end = ';'
|
||||||
|
if d.kind == 'function' or d.kind == 'variable':
|
||||||
|
text += d.type + ' ' if len(d.type) > 0 else ''
|
||||||
|
elif d.kind == 'typedef':
|
||||||
|
text += 'using '
|
||||||
|
elif d.kind == 'define':
|
||||||
|
end = ''
|
||||||
|
else:
|
||||||
|
text += d.kind + ' '
|
||||||
|
text += d.name
|
||||||
|
|
||||||
|
if d.params is not None:
|
||||||
|
params = ', '.join([
|
||||||
|
(p.type + ' ' if p.type else '') + p.name for p in d.params])
|
||||||
|
text += '(' + escape_html(params) + ')'
|
||||||
|
if d.trailing_return_type:
|
||||||
|
text += ' -⁠> ' + escape_html(d.trailing_return_type)
|
||||||
|
elif d.kind == 'typedef':
|
||||||
|
text += ' = ' + escape_html(d.type)
|
||||||
|
|
||||||
|
text += end
|
||||||
|
text += '</div>'
|
||||||
|
text += '</code></pre>\n'
|
||||||
|
if d.id is not None:
|
||||||
|
text += f'</a>\n'
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def get_handler(theme: str, custom_templates: Optional[str] = None,
|
|
||||||
**config: Any) -> CxxHandler:
|
|
||||||
'''Return an instance of `CxxHandler`.
|
|
||||||
|
|
||||||
Arguments:
|
class CxxHandler(BaseHandler):
|
||||||
theme: The theme to use when rendering contents.
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
custom_templates: Directory containing custom templates.
|
super().__init__(handler='cxx', **kwargs)
|
||||||
**config: Configuration passed to the handler.
|
|
||||||
'''
|
headers = [
|
||||||
return CxxHandler(theme=theme, custom_templates=custom_templates)
|
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
|
||||||
|
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Run doxygen.
|
||||||
|
cmd = ['doxygen', '-']
|
||||||
|
support_dir = Path(__file__).parents[3]
|
||||||
|
top_dir = os.path.dirname(support_dir)
|
||||||
|
include_dir = os.path.join(top_dir, 'include', 'fmt')
|
||||||
|
self._ns2doxyxml = {}
|
||||||
|
build_dir = os.path.join(top_dir, 'build')
|
||||||
|
os.makedirs(build_dir, exist_ok=True)
|
||||||
|
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
|
||||||
|
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
||||||
|
_, _ = p.communicate(input=r'''
|
||||||
|
PROJECT_NAME = fmt
|
||||||
|
GENERATE_XML = YES
|
||||||
|
GENERATE_LATEX = NO
|
||||||
|
GENERATE_HTML = NO
|
||||||
|
INPUT = {0}
|
||||||
|
XML_OUTPUT = {1}
|
||||||
|
QUIET = YES
|
||||||
|
AUTOLINK_SUPPORT = NO
|
||||||
|
MACRO_EXPANSION = YES
|
||||||
|
PREDEFINED = _WIN32=1 \
|
||||||
|
__linux__=1 \
|
||||||
|
FMT_ENABLE_IF(...)= \
|
||||||
|
FMT_USE_USER_LITERALS=1 \
|
||||||
|
FMT_USE_ALIAS_TEMPLATES=1 \
|
||||||
|
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
|
||||||
|
FMT_API= \
|
||||||
|
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||||
|
"FMT_END_NAMESPACE=}}" \
|
||||||
|
"FMT_DOC=1"
|
||||||
|
'''.format(
|
||||||
|
' '.join([os.path.join(include_dir, h) for h in headers]),
|
||||||
|
self._doxyxml_dir).encode('utf-8'))
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise CalledProcessError(p.returncode, cmd)
|
||||||
|
|
||||||
|
# Merge all file-level XMLs into one to simplify search.
|
||||||
|
self._file_doxyxml = None
|
||||||
|
for h in headers:
|
||||||
|
filename = h.replace(".h", "_8h.xml")
|
||||||
|
with open(os.path.join(self._doxyxml_dir, filename)) as f:
|
||||||
|
doxyxml = ElementTree.parse(f)
|
||||||
|
if self._file_doxyxml is None:
|
||||||
|
self._file_doxyxml = doxyxml
|
||||||
|
continue
|
||||||
|
root = self._file_doxyxml.getroot()
|
||||||
|
for node in doxyxml.getroot():
|
||||||
|
root.append(node)
|
||||||
|
|
||||||
|
def collect_compound(self, identifier: str,
|
||||||
|
cls: List[ElementTree.Element]) -> Definition:
|
||||||
|
"""Collect a compound definition such as a struct."""
|
||||||
|
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
|
||||||
|
with open(path) as f:
|
||||||
|
xml = ElementTree.parse(f)
|
||||||
|
node = xml.find('compounddef')
|
||||||
|
d = Definition(identifier, node=node)
|
||||||
|
d.template_params = convert_template_params(node)
|
||||||
|
d.desc = get_description(node)
|
||||||
|
d.members = []
|
||||||
|
for m in \
|
||||||
|
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
|
||||||
|
node.findall('sectiondef[@kind="public-func"]/memberdef'):
|
||||||
|
name = m.find('name').text
|
||||||
|
# Doxygen incorrectly classifies members of private unnamed unions as
|
||||||
|
# public members of the containing class.
|
||||||
|
if name.endswith('_'):
|
||||||
|
continue
|
||||||
|
desc = get_description(m)
|
||||||
|
if len(desc) == 0:
|
||||||
|
continue
|
||||||
|
kind = m.get('kind')
|
||||||
|
member = Definition(name if name else '', kind=kind, is_member=True)
|
||||||
|
type_text = m.find('type').text
|
||||||
|
member.type = type_text if type_text else ''
|
||||||
|
if kind == 'function':
|
||||||
|
member.params = convert_params(m)
|
||||||
|
convert_return_type(member, m)
|
||||||
|
member.template_params = None
|
||||||
|
member.desc = desc
|
||||||
|
d.members.append(member)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
|
||||||
|
qual_name = 'fmt::' + identifier
|
||||||
|
|
||||||
|
param_str = None
|
||||||
|
paren = qual_name.find('(')
|
||||||
|
if paren > 0:
|
||||||
|
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
|
||||||
|
|
||||||
|
colons = qual_name.rfind('::')
|
||||||
|
namespace, name = qual_name[:colons], qual_name[colons + 2:]
|
||||||
|
|
||||||
|
# Load XML.
|
||||||
|
doxyxml = self._ns2doxyxml.get(namespace)
|
||||||
|
if doxyxml is None:
|
||||||
|
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
|
||||||
|
with open(os.path.join(self._doxyxml_dir, path)) as f:
|
||||||
|
doxyxml = ElementTree.parse(f)
|
||||||
|
self._ns2doxyxml[namespace] = doxyxml
|
||||||
|
|
||||||
|
nodes = doxyxml.findall(
|
||||||
|
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||||
|
if len(nodes) == 0:
|
||||||
|
nodes = self._file_doxyxml.findall(
|
||||||
|
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||||
|
candidates = []
|
||||||
|
for node in nodes:
|
||||||
|
# Process a function or a typedef.
|
||||||
|
params = None
|
||||||
|
d = Definition(name, node=node)
|
||||||
|
if d.kind == 'function':
|
||||||
|
params = convert_params(node)
|
||||||
|
node_param_str = ', '.join([p.type for p in params])
|
||||||
|
if param_str and param_str != node_param_str:
|
||||||
|
candidates.append(f'{name}({node_param_str})')
|
||||||
|
continue
|
||||||
|
elif d.kind == 'define':
|
||||||
|
params = []
|
||||||
|
for p in node.findall('param'):
|
||||||
|
param = Definition(p.find('defname').text, kind='param')
|
||||||
|
param.type = None
|
||||||
|
params.append(param)
|
||||||
|
d.type = convert_type(node.find('type'))
|
||||||
|
d.template_params = convert_template_params(node)
|
||||||
|
d.params = params
|
||||||
|
convert_return_type(d, node)
|
||||||
|
d.desc = get_description(node)
|
||||||
|
return d
|
||||||
|
|
||||||
|
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
|
||||||
|
if not cls:
|
||||||
|
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
|
||||||
|
return self.collect_compound(identifier, cls)
|
||||||
|
|
||||||
|
def render(self, d: Definition, config: dict) -> str:
|
||||||
|
if d.id is not None:
|
||||||
|
self.do_heading('', 0, id=d.id)
|
||||||
|
text = '<div class="docblock">\n'
|
||||||
|
text += render_decl(d)
|
||||||
|
text += '<div class="docblock-desc">\n'
|
||||||
|
text += doxyxml2html(d.desc)
|
||||||
|
if d.members is not None:
|
||||||
|
for m in d.members:
|
||||||
|
text += self.render(m, config)
|
||||||
|
text += '</div>\n'
|
||||||
|
text += '</div>\n'
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def get_handler(theme: str, custom_templates: Optional[str] = None,
|
||||||
|
**_config: Any) -> CxxHandler:
|
||||||
|
"""Return an instance of `CxxHandler`.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
theme: The theme to use when rendering contents.
|
||||||
|
custom_templates: Directory containing custom templates.
|
||||||
|
**_config: Configuration passed to the handler.
|
||||||
|
"""
|
||||||
|
return CxxHandler(theme=theme, custom_templates=custom_templates)
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
"""Manage site and releases.
|
"""Make a release.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
manage.py release [<branch>]
|
release.py [<branch>]
|
||||||
manage.py site
|
|
||||||
|
|
||||||
For the release command $FMT_TOKEN should contain a GitHub personal access token
|
For the release command $FMT_TOKEN should contain a GitHub personal access token
|
||||||
obtained from https://github.com/settings/tokens.
|
obtained from https://github.com/settings/tokens.
|
||||||
@ -12,9 +11,9 @@ obtained from https://github.com/settings/tokens.
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import datetime, docopt, errno, fileinput, json, os
|
import datetime, docopt, errno, fileinput, json, os
|
||||||
import re, requests, shutil, sys
|
import re, shutil, sys
|
||||||
from contextlib import contextmanager
|
|
||||||
from subprocess import check_call
|
from subprocess import check_call
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
class Git:
|
class Git:
|
||||||
@ -81,46 +80,15 @@ def create_build_env():
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
fmt_repo_url = 'git@github.com:fmtlib/fmt'
|
if __name__ == '__main__':
|
||||||
|
args = docopt.docopt(__doc__)
|
||||||
|
|
||||||
def update_site(env):
|
|
||||||
env.fmt_repo.update(fmt_repo_url)
|
|
||||||
|
|
||||||
doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev'))
|
|
||||||
doc_repo.update('git@github.com:fmtlib/fmt.dev')
|
|
||||||
|
|
||||||
version = '11.0.0'
|
|
||||||
clean_checkout(env.fmt_repo, version)
|
|
||||||
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
|
|
||||||
|
|
||||||
# Build the docs.
|
|
||||||
html_dir = os.path.join(env.build_dir, 'html')
|
|
||||||
if os.path.exists(html_dir):
|
|
||||||
shutil.rmtree(html_dir)
|
|
||||||
include_dir = env.fmt_repo.dir
|
|
||||||
import build
|
|
||||||
build.build_docs(version, doc_dir=target_doc_dir,
|
|
||||||
include_dir=include_dir, work_dir=env.build_dir)
|
|
||||||
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
|
|
||||||
# Copy docs to the website.
|
|
||||||
version_doc_dir = os.path.join(doc_repo.dir, version)
|
|
||||||
try:
|
|
||||||
shutil.rmtree(version_doc_dir)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno != errno.ENOENT:
|
|
||||||
raise
|
|
||||||
shutil.move(html_dir, version_doc_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def release(args):
|
|
||||||
env = create_build_env()
|
env = create_build_env()
|
||||||
fmt_repo = env.fmt_repo
|
fmt_repo = env.fmt_repo
|
||||||
|
|
||||||
branch = args.get('<branch>')
|
branch = args.get('<branch>')
|
||||||
if branch is None:
|
if branch is None:
|
||||||
branch = 'master'
|
branch = 'master'
|
||||||
if not fmt_repo.update('-b', branch, fmt_repo_url):
|
if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
|
||||||
clean_checkout(fmt_repo, branch)
|
clean_checkout(fmt_repo, branch)
|
||||||
|
|
||||||
# Update the date in the changelog and extract the version and the first
|
# Update the date in the changelog and extract the version and the first
|
||||||
@ -191,28 +159,30 @@ def release(args):
|
|||||||
# Create a release on GitHub.
|
# Create a release on GitHub.
|
||||||
fmt_repo.push('origin', 'release')
|
fmt_repo.push('origin', 'release')
|
||||||
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
|
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
|
||||||
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
|
req = urllib.request.Request(
|
||||||
headers=auth_headers,
|
'https://api.github.com/repos/fmtlib/fmt/releases',
|
||||||
data=json.dumps({'tag_name': version,
|
data=json.dumps({'tag_name': version,
|
||||||
'target_commitish': 'release',
|
'target_commitish': 'release',
|
||||||
'body': changes, 'draft': True}))
|
'body': changes, 'draft': True}).encode('utf-8'),
|
||||||
if r.status_code != 201:
|
headers=auth_headers, method='POST')
|
||||||
raise Exception('Failed to create a release ' + str(r))
|
with urllib.request.urlopen(req) as response:
|
||||||
id = r.json()['id']
|
if response.status != 201:
|
||||||
|
raise Exception(f'Failed to create a release ' +
|
||||||
|
'{response.status} {response.reason}')
|
||||||
|
response_data = json.loads(response.read().decode('utf-8'))
|
||||||
|
id = response_data['id']
|
||||||
|
|
||||||
|
# Upload the package.
|
||||||
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
|
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
|
||||||
package = 'fmt-{}.zip'.format(version)
|
package = 'fmt-{}.zip'.format(version)
|
||||||
r = requests.post(
|
req = urllib.request.Request(
|
||||||
'{}/{}/assets?name={}'.format(uploads_url, id, package),
|
f'{uploads_url}/{id}/assets?name={package}',
|
||||||
headers={'Content-Type': 'application/zip'} | auth_headers,
|
headers={'Content-Type': 'application/zip'} | auth_headers,
|
||||||
data=open('build/fmt/' + package, 'rb'))
|
data=open('build/fmt/' + package, 'rb').read(), method='POST')
|
||||||
if r.status_code != 201:
|
with urllib.request.urlopen(req) as response:
|
||||||
raise Exception('Failed to upload an asset ' + str(r))
|
if response.status != 201:
|
||||||
|
raise Exception(f'Failed to upload an asset '
|
||||||
|
'{response.status} {response.reason}')
|
||||||
|
|
||||||
update_site(env)
|
short_version = '.'.join(version.split('.')[:-1])
|
||||||
|
check_call(['./mkdocs', 'deploy', short_version])
|
||||||
if __name__ == '__main__':
|
|
||||||
args = docopt.docopt(__doc__)
|
|
||||||
if args.get('release'):
|
|
||||||
release(args)
|
|
||||||
elif args.get('site'):
|
|
||||||
update_site(create_build_env())
|
|
||||||
@ -8,22 +8,6 @@ target_include_directories(test-main PUBLIC
|
|||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||||
target_link_libraries(test-main gtest fmt)
|
target_link_libraries(test-main gtest fmt)
|
||||||
|
|
||||||
function(add_fmt_executable name)
|
|
||||||
add_executable(${name} ${ARGN})
|
|
||||||
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
|
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
|
|
||||||
# Bogus -Wstringop-overflow warning
|
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100395
|
|
||||||
# [10 Regression] spurious -Wstringop-overflow writing to a trailing array plus offset
|
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
|
|
||||||
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
|
|
||||||
target_compile_options(${name} PRIVATE -Wno-stringop-overflow)
|
|
||||||
# The linker flag is needed for LTO.
|
|
||||||
target_link_libraries(${name} -Wno-stringop-overflow)
|
|
||||||
endif ()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# Adds a test.
|
# Adds a test.
|
||||||
# Usage: add_fmt_test(name srcs...)
|
# Usage: add_fmt_test(name srcs...)
|
||||||
function(add_fmt_test name)
|
function(add_fmt_test name)
|
||||||
@ -42,7 +26,7 @@ function(add_fmt_test name)
|
|||||||
else ()
|
else ()
|
||||||
set(libs test-main fmt)
|
set(libs test-main fmt)
|
||||||
endif ()
|
endif ()
|
||||||
add_fmt_executable(${name} ${sources})
|
add_executable(${name} ${sources})
|
||||||
target_link_libraries(${name} ${libs})
|
target_link_libraries(${name} ${libs})
|
||||||
|
|
||||||
if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE)
|
if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE)
|
||||||
@ -78,13 +62,9 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
|||||||
endif ()
|
endif ()
|
||||||
add_fmt_test(ostream-test)
|
add_fmt_test(ostream-test)
|
||||||
add_fmt_test(compile-test)
|
add_fmt_test(compile-test)
|
||||||
add_fmt_test(compile-fp-test HEADER_ONLY)
|
|
||||||
if (MSVC)
|
|
||||||
# Without this option, MSVC returns 199711L for the __cplusplus macro.
|
|
||||||
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
|
|
||||||
endif()
|
|
||||||
add_fmt_test(printf-test)
|
add_fmt_test(printf-test)
|
||||||
add_fmt_test(ranges-test ranges-odr-test.cc)
|
add_fmt_test(ranges-test ranges-odr-test.cc)
|
||||||
|
add_fmt_test(no-builtin-types-test HEADER_ONLY)
|
||||||
|
|
||||||
add_fmt_test(scan-test HEADER_ONLY)
|
add_fmt_test(scan-test HEADER_ONLY)
|
||||||
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
|
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
|
||||||
@ -110,6 +90,9 @@ add_fmt_test(enforce-checks-test)
|
|||||||
target_compile_definitions(enforce-checks-test PRIVATE
|
target_compile_definitions(enforce-checks-test PRIVATE
|
||||||
-DFMT_ENFORCE_COMPILE_STRING)
|
-DFMT_ENFORCE_COMPILE_STRING)
|
||||||
|
|
||||||
|
add_executable(perf-sanity perf-sanity.cc)
|
||||||
|
target_link_libraries(perf-sanity fmt::fmt)
|
||||||
|
|
||||||
if (FMT_MODULE)
|
if (FMT_MODULE)
|
||||||
# The tests need {fmt} to be compiled as traditional library
|
# The tests need {fmt} to be compiled as traditional library
|
||||||
# because of visibility of implementation details.
|
# because of visibility of implementation details.
|
||||||
@ -142,7 +125,7 @@ if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT MSVC_STATIC_RUNTIME)
|
if (NOT MSVC_STATIC_RUNTIME)
|
||||||
add_fmt_executable(posix-mock-test
|
add_executable(posix-mock-test
|
||||||
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||||
@ -233,7 +216,7 @@ if (FMT_PEDANTIC AND NOT WIN32 AND NOT (
|
|||||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# This test are disabled on Windows because it is only *NIX issue.
|
# This test is disabled on Windows because it is POSIX-specific.
|
||||||
if (FMT_PEDANTIC AND NOT WIN32)
|
if (FMT_PEDANTIC AND NOT WIN32)
|
||||||
add_test(static-export-test ${CMAKE_CTEST_COMMAND}
|
add_test(static-export-test ${CMAKE_CTEST_COMMAND}
|
||||||
-C ${CMAKE_BUILD_TYPE}
|
-C ${CMAKE_BUILD_TYPE}
|
||||||
|
|||||||
@ -64,7 +64,7 @@ TEST(args_test, custom_format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct to_stringable {
|
struct to_stringable {
|
||||||
friend fmt::string_view to_string_view(to_stringable) { return {}; }
|
friend auto to_string_view(to_stringable) -> fmt::string_view { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
@ -186,3 +186,17 @@ TEST(args_test, move_constructor) {
|
|||||||
store.reset();
|
store.reset();
|
||||||
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
|
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(args_test, size) {
|
||||||
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
|
EXPECT_EQ(store.size(), 0);
|
||||||
|
|
||||||
|
store.push_back(42);
|
||||||
|
EXPECT_EQ(store.size(), 1);
|
||||||
|
|
||||||
|
store.push_back("Molybdenum");
|
||||||
|
EXPECT_EQ(store.size(), 2);
|
||||||
|
|
||||||
|
store.clear();
|
||||||
|
EXPECT_EQ(store.size(), 0);
|
||||||
|
}
|
||||||
|
|||||||
@ -5,14 +5,16 @@
|
|||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
// Turn assertion failures into exceptions for testing.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
#include "test-assert.h"
|
#include "test-assert.h"
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#include "fmt/base.h"
|
#include "fmt/base.h"
|
||||||
|
|
||||||
#include <climits> // INT_MAX
|
#include <limits.h> // INT_MAX
|
||||||
#include <cstring> // std::strlen
|
#include <string.h> // strlen
|
||||||
|
|
||||||
#include <functional> // std::equal_to
|
#include <functional> // std::equal_to
|
||||||
#include <iterator> // std::back_insert_iterator, std::distance
|
#include <iterator> // std::back_insert_iterator, std::distance
|
||||||
#include <limits> // std::numeric_limits
|
#include <limits> // std::numeric_limits
|
||||||
@ -21,39 +23,36 @@
|
|||||||
|
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
using fmt::string_view;
|
#ifdef FMT_FORMAT_H_
|
||||||
using fmt::detail::buffer;
|
# error base-test includes format.h
|
||||||
|
#endif
|
||||||
|
|
||||||
using testing::_;
|
using testing::_;
|
||||||
using testing::Invoke;
|
using testing::Invoke;
|
||||||
using testing::Return;
|
using testing::Return;
|
||||||
|
|
||||||
#ifdef FMT_FORMAT_H_
|
auto copy(fmt::string_view s, fmt::appender out) -> fmt::appender {
|
||||||
# error core-test includes format.h
|
|
||||||
#endif
|
|
||||||
|
|
||||||
fmt::appender copy(fmt::string_view s, fmt::appender out) {
|
|
||||||
for (char c : s) *out++ = c;
|
for (char c : s) *out++ = c;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(string_view_test, value_type) {
|
TEST(string_view_test, value_type) {
|
||||||
static_assert(std::is_same<string_view::value_type, char>::value, "");
|
static_assert(std::is_same<fmt::string_view::value_type, char>::value, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(string_view_test, ctor) {
|
TEST(string_view_test, ctor) {
|
||||||
EXPECT_STREQ("abc", fmt::string_view("abc").data());
|
EXPECT_STREQ(fmt::string_view("abc").data(), "abc");
|
||||||
EXPECT_EQ(3u, fmt::string_view("abc").size());
|
EXPECT_EQ(fmt::string_view("abc").size(), 3u);
|
||||||
|
|
||||||
EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data());
|
EXPECT_STREQ(fmt::string_view(std::string("defg")).data(), "defg");
|
||||||
EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size());
|
EXPECT_EQ(fmt::string_view(std::string("defg")).size(), 4u);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(string_view_test, length) {
|
TEST(string_view_test, length) {
|
||||||
// Test that string_view::size() returns string length, not buffer size.
|
// Test that string_view::size() returns string length, not buffer size.
|
||||||
char str[100] = "some string";
|
char str[100] = "some string";
|
||||||
EXPECT_EQ(std::strlen(str), string_view(str).size());
|
EXPECT_EQ(fmt::string_view(str).size(), strlen(str));
|
||||||
EXPECT_LT(std::strlen(str), sizeof(str));
|
EXPECT_LT(strlen(str), sizeof(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check string_view's comparison operator.
|
// Check string_view's comparison operator.
|
||||||
@ -62,13 +61,16 @@ template <template <typename> class Op> void check_op() {
|
|||||||
size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
|
size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
|
||||||
for (size_t i = 0; i < num_inputs; ++i) {
|
for (size_t i = 0; i < num_inputs; ++i) {
|
||||||
for (size_t j = 0; j < num_inputs; ++j) {
|
for (size_t j = 0; j < num_inputs; ++j) {
|
||||||
string_view lhs(inputs[i]), rhs(inputs[j]);
|
fmt::string_view lhs(inputs[i]), rhs(inputs[j]);
|
||||||
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs));
|
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0),
|
||||||
|
Op<fmt::string_view>()(lhs, rhs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(string_view_test, compare) {
|
TEST(string_view_test, compare) {
|
||||||
|
using fmt::string_view;
|
||||||
|
|
||||||
EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
|
EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
|
||||||
EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
|
EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
|
||||||
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
||||||
@ -92,64 +94,49 @@ TEST(string_view_test, compare) {
|
|||||||
check_op<std::greater_equal>();
|
check_op<std::greater_equal>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(base_test, is_output_iterator) {
|
#if FMT_USE_CONSTEVAL
|
||||||
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
|
TEST(string_view_test, from_constexpr_fixed_string) {
|
||||||
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
|
constexpr int size = 4;
|
||||||
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
|
|
||||||
EXPECT_TRUE(
|
|
||||||
(fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
|
|
||||||
char>::value));
|
|
||||||
EXPECT_TRUE(
|
|
||||||
(fmt::detail::is_output_iterator<std::string::iterator, char>::value));
|
|
||||||
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
|
|
||||||
char>::value));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base_test, is_back_insert_iterator) {
|
struct fixed_string {
|
||||||
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<
|
char data[size] = {};
|
||||||
std::back_insert_iterator<std::string>>::value);
|
|
||||||
EXPECT_FALSE(fmt::detail::is_back_insert_iterator<
|
|
||||||
std::front_insert_iterator<std::string>>::value);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base_test, buffer_appender) {
|
constexpr fixed_string(const char (&m)[size]) {
|
||||||
#ifdef __cpp_lib_ranges
|
for (size_t i = 0; i != size; ++i) data[i] = m[i];
|
||||||
static_assert(std::output_iterator<fmt::appender, char>);
|
}
|
||||||
#endif
|
};
|
||||||
}
|
|
||||||
|
static constexpr auto fs = fixed_string("foo");
|
||||||
|
static constexpr auto sv = fmt::string_view(fs.data);
|
||||||
|
EXPECT_EQ(sv, "foo");
|
||||||
|
}
|
||||||
|
#endif // FMT_USE_CONSTEVAL
|
||||||
|
|
||||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
|
|
||||||
TEST(buffer_test, noncopyable) {
|
TEST(buffer_test, noncopyable) {
|
||||||
EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value);
|
EXPECT_FALSE(std::is_copy_constructible<fmt::detail::buffer<char>>::value);
|
||||||
# if !FMT_MSC_VERSION
|
EXPECT_FALSE(std::is_copy_assignable<fmt::detail::buffer<char>>::value);
|
||||||
// std::is_copy_assignable is broken in MSVC2013.
|
|
||||||
EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value);
|
|
||||||
# endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(buffer_test, nonmoveable) {
|
TEST(buffer_test, nonmoveable) {
|
||||||
EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value);
|
EXPECT_FALSE(std::is_move_constructible<fmt::detail::buffer<char>>::value);
|
||||||
# if !FMT_MSC_VERSION
|
EXPECT_FALSE(std::is_move_assignable<fmt::detail::buffer<char>>::value);
|
||||||
// std::is_move_assignable is broken in MSVC2013.
|
|
||||||
EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value);
|
|
||||||
# endif
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(buffer_test, indestructible) {
|
TEST(buffer_test, indestructible) {
|
||||||
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
|
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
|
||||||
"buffer's destructor is protected");
|
"buffer's destructor is protected");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct mock_buffer final : buffer<T> {
|
template <typename T> struct mock_buffer final : fmt::detail::buffer<T> {
|
||||||
MOCK_METHOD(size_t, do_grow, (size_t));
|
MOCK_METHOD(size_t, do_grow, (size_t));
|
||||||
|
|
||||||
static void grow(buffer<T>& buf, size_t capacity) {
|
static void grow(fmt::detail::buffer<T>& buf, size_t capacity) {
|
||||||
auto& self = static_cast<mock_buffer&>(buf);
|
auto& self = static_cast<mock_buffer&>(buf);
|
||||||
self.set(buf.data(), self.do_grow(capacity));
|
self.set(buf.data(), self.do_grow(capacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
mock_buffer(T* data = nullptr, size_t buf_capacity = 0) : buffer<T>(grow) {
|
mock_buffer(T* data = nullptr, size_t buf_capacity = 0)
|
||||||
|
: fmt::detail::buffer<T>(grow) {
|
||||||
this->set(data, buf_capacity);
|
this->set(data, buf_capacity);
|
||||||
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
|
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
|
||||||
return capacity;
|
return capacity;
|
||||||
@ -160,24 +147,24 @@ template <typename T> struct mock_buffer final : buffer<T> {
|
|||||||
TEST(buffer_test, ctor) {
|
TEST(buffer_test, ctor) {
|
||||||
{
|
{
|
||||||
mock_buffer<int> buffer;
|
mock_buffer<int> buffer;
|
||||||
EXPECT_EQ(nullptr, buffer.data());
|
EXPECT_EQ(buffer.data(), nullptr);
|
||||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
EXPECT_EQ(buffer.size(), 0u);
|
||||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
EXPECT_EQ(buffer.capacity(), 0u);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int dummy;
|
int data;
|
||||||
mock_buffer<int> buffer(&dummy);
|
mock_buffer<int> buffer(&data);
|
||||||
EXPECT_EQ(&dummy, &buffer[0]);
|
EXPECT_EQ(&buffer[0], &data);
|
||||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
EXPECT_EQ(buffer.size(), 0u);
|
||||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
EXPECT_EQ(buffer.capacity(), 0u);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
int dummy;
|
int data;
|
||||||
size_t capacity = std::numeric_limits<size_t>::max();
|
size_t capacity = std::numeric_limits<size_t>::max();
|
||||||
mock_buffer<int> buffer(&dummy, capacity);
|
mock_buffer<int> buffer(&data, capacity);
|
||||||
EXPECT_EQ(&dummy, &buffer[0]);
|
EXPECT_EQ(&buffer[0], &data);
|
||||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
EXPECT_EQ(buffer.size(), 0u);
|
||||||
EXPECT_EQ(capacity, buffer.capacity());
|
EXPECT_EQ(buffer.capacity(), capacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,26 +172,26 @@ TEST(buffer_test, access) {
|
|||||||
char data[10];
|
char data[10];
|
||||||
mock_buffer<char> buffer(data, sizeof(data));
|
mock_buffer<char> buffer(data, sizeof(data));
|
||||||
buffer[0] = 11;
|
buffer[0] = 11;
|
||||||
EXPECT_EQ(11, buffer[0]);
|
EXPECT_EQ(buffer[0], 11);
|
||||||
buffer[3] = 42;
|
buffer[3] = 42;
|
||||||
EXPECT_EQ(42, *(&buffer[0] + 3));
|
EXPECT_EQ(*(&buffer[0] + 3), 42);
|
||||||
const fmt::detail::buffer<char>& const_buffer = buffer;
|
const fmt::detail::buffer<char>& const_buffer = buffer;
|
||||||
EXPECT_EQ(42, const_buffer[3]);
|
EXPECT_EQ(const_buffer[3], 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(buffer_test, try_resize) {
|
TEST(buffer_test, try_resize) {
|
||||||
char data[123];
|
char data[123];
|
||||||
mock_buffer<char> buffer(data, sizeof(data));
|
mock_buffer<char> buffer(data, sizeof(data));
|
||||||
buffer[10] = 42;
|
buffer[10] = 42;
|
||||||
EXPECT_EQ(42, buffer[10]);
|
EXPECT_EQ(buffer[10], 42);
|
||||||
buffer.try_resize(20);
|
buffer.try_resize(20);
|
||||||
EXPECT_EQ(20u, buffer.size());
|
EXPECT_EQ(buffer.size(), 20u);
|
||||||
EXPECT_EQ(123u, buffer.capacity());
|
EXPECT_EQ(buffer.capacity(), 123u);
|
||||||
EXPECT_EQ(42, buffer[10]);
|
EXPECT_EQ(buffer[10], 42);
|
||||||
buffer.try_resize(5);
|
buffer.try_resize(5);
|
||||||
EXPECT_EQ(5u, buffer.size());
|
EXPECT_EQ(buffer.size(), 5u);
|
||||||
EXPECT_EQ(123u, buffer.capacity());
|
EXPECT_EQ(buffer.capacity(), 123u);
|
||||||
EXPECT_EQ(42, buffer[10]);
|
EXPECT_EQ(buffer[10], 42);
|
||||||
// Check if try_resize calls grow.
|
// Check if try_resize calls grow.
|
||||||
EXPECT_CALL(buffer, do_grow(124));
|
EXPECT_CALL(buffer, do_grow(124));
|
||||||
buffer.try_resize(124);
|
buffer.try_resize(124);
|
||||||
@ -226,8 +213,8 @@ TEST(buffer_test, clear) {
|
|||||||
EXPECT_CALL(buffer, do_grow(20));
|
EXPECT_CALL(buffer, do_grow(20));
|
||||||
buffer.try_resize(20);
|
buffer.try_resize(20);
|
||||||
buffer.try_resize(0);
|
buffer.try_resize(0);
|
||||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
EXPECT_EQ(buffer.size(), 0u);
|
||||||
EXPECT_EQ(20u, buffer.capacity());
|
EXPECT_EQ(buffer.capacity(), 20u);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(buffer_test, append) {
|
TEST(buffer_test, append) {
|
||||||
@ -235,14 +222,14 @@ TEST(buffer_test, append) {
|
|||||||
mock_buffer<char> buffer(data, 10);
|
mock_buffer<char> buffer(data, 10);
|
||||||
auto test = "test";
|
auto test = "test";
|
||||||
buffer.append(test, test + 5);
|
buffer.append(test, test + 5);
|
||||||
EXPECT_STREQ(test, &buffer[0]);
|
EXPECT_STREQ(&buffer[0], test);
|
||||||
EXPECT_EQ(5u, buffer.size());
|
EXPECT_EQ(buffer.size(), 5u);
|
||||||
buffer.try_resize(10);
|
buffer.try_resize(10);
|
||||||
EXPECT_CALL(buffer, do_grow(12));
|
EXPECT_CALL(buffer, do_grow(12));
|
||||||
buffer.append(test, test + 2);
|
buffer.append(test, test + 2);
|
||||||
EXPECT_EQ('t', buffer[10]);
|
EXPECT_EQ(buffer[10], 't');
|
||||||
EXPECT_EQ('e', buffer[11]);
|
EXPECT_EQ(buffer[11], 'e');
|
||||||
EXPECT_EQ(12u, buffer.size());
|
EXPECT_EQ(buffer.size(), 12u);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(buffer_test, append_partial) {
|
TEST(buffer_test, append_partial) {
|
||||||
@ -268,27 +255,51 @@ TEST(buffer_test, append_allocates_enough_storage) {
|
|||||||
buffer.append(test, test + 9);
|
buffer.append(test, test + 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct custom_context {
|
TEST(base_test, is_locking) {
|
||||||
using char_type = char;
|
EXPECT_FALSE(fmt::detail::is_locking<const char(&)[3]>());
|
||||||
using parse_context_type = fmt::format_parse_context;
|
}
|
||||||
|
|
||||||
bool called = false;
|
TEST(base_test, is_output_iterator) {
|
||||||
|
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
|
||||||
|
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
|
||||||
|
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
(fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
|
||||||
|
char>::value));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
(fmt::detail::is_output_iterator<std::string::iterator, char>::value));
|
||||||
|
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
|
||||||
|
char>::value));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> struct formatter_type {
|
TEST(base_test, is_back_insert_iterator) {
|
||||||
FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx)
|
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<
|
||||||
-> decltype(ctx.begin()) {
|
std::back_insert_iterator<std::string>>::value);
|
||||||
return ctx.begin();
|
EXPECT_FALSE(fmt::detail::is_back_insert_iterator<
|
||||||
}
|
std::front_insert_iterator<std::string>>::value);
|
||||||
|
}
|
||||||
|
|
||||||
const char* format(const T&, custom_context& ctx) const {
|
struct minimal_container {
|
||||||
ctx.called = true;
|
using value_type = char;
|
||||||
return nullptr;
|
void push_back(char) {}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void advance_to(const char*) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TEST(base_test, copy) {
|
||||||
|
minimal_container c;
|
||||||
|
static constexpr char str[] = "a";
|
||||||
|
fmt::detail::copy<char>(str, str + 1, std::back_inserter(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base_test, get_buffer) {
|
||||||
|
mock_buffer<char> buffer;
|
||||||
|
void* buffer_ptr = &buffer;
|
||||||
|
auto&& appender_result = fmt::detail::get_buffer<char>(fmt::appender(buffer));
|
||||||
|
EXPECT_EQ(&appender_result, buffer_ptr);
|
||||||
|
auto&& back_inserter_result =
|
||||||
|
fmt::detail::get_buffer<char>(std::back_inserter(buffer));
|
||||||
|
EXPECT_EQ(&back_inserter_result, buffer_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
struct test_struct {};
|
struct test_struct {};
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
@ -303,21 +314,6 @@ template <typename Char> struct formatter<test_struct, Char> {
|
|||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(arg_test, format_args) {
|
|
||||||
auto args = fmt::format_args();
|
|
||||||
EXPECT_FALSE(args.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(arg_test, make_value_with_custom_context) {
|
|
||||||
auto t = test_struct();
|
|
||||||
auto arg = fmt::detail::value<custom_context>(
|
|
||||||
fmt::detail::arg_mapper<custom_context>().map(t));
|
|
||||||
auto ctx = custom_context();
|
|
||||||
auto parse_ctx = fmt::format_parse_context("");
|
|
||||||
arg.custom.format(&t, parse_ctx, ctx);
|
|
||||||
EXPECT_TRUE(ctx.called);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a unique result type to make sure that there are no undesirable
|
// Use a unique result type to make sure that there are no undesirable
|
||||||
// conversions.
|
// conversions.
|
||||||
struct test_result {};
|
struct test_result {};
|
||||||
@ -364,29 +360,99 @@ VISIT_TYPE(long, long long);
|
|||||||
VISIT_TYPE(unsigned long, unsigned long long);
|
VISIT_TYPE(unsigned long, unsigned long long);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CHECK_ARG(Char, expected, value) \
|
#if FMT_BUILTIN_TYPES
|
||||||
{ \
|
# define CHECK_ARG(expected, value) \
|
||||||
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
{ \
|
||||||
EXPECT_CALL(visitor, visit(expected)); \
|
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
||||||
using iterator = fmt::basic_appender<Char>; \
|
EXPECT_CALL(visitor, visit(expected)); \
|
||||||
auto var = value; \
|
auto var = value; \
|
||||||
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>(var) \
|
fmt::basic_format_arg<fmt::format_context>(var).visit(visitor); \
|
||||||
.visit(visitor); \
|
}
|
||||||
}
|
#else
|
||||||
|
# define CHECK_ARG(expected, value)
|
||||||
|
#endif
|
||||||
|
|
||||||
#define CHECK_ARG_SIMPLE(value) \
|
#define CHECK_ARG_SIMPLE(value) \
|
||||||
{ \
|
{ \
|
||||||
using value_type = decltype(value); \
|
using value_type = decltype(value); \
|
||||||
typename visit_type<value_type>::type expected = value; \
|
typename visit_type<value_type>::type expected = value; \
|
||||||
CHECK_ARG(char, expected, value) \
|
CHECK_ARG(expected, value) \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(arg_test, format_args) {
|
||||||
|
auto args = fmt::format_args();
|
||||||
|
EXPECT_FALSE(args.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(arg_test, char_arg) { CHECK_ARG('a', 'a'); }
|
||||||
|
|
||||||
|
TEST(arg_test, string_arg) {
|
||||||
|
char str_data[] = "test";
|
||||||
|
char* str = str_data;
|
||||||
|
const char* cstr = str;
|
||||||
|
CHECK_ARG(cstr, str);
|
||||||
|
|
||||||
|
auto sv = fmt::string_view(str);
|
||||||
|
CHECK_ARG(sv, std::string(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(arg_test, pointer_arg) {
|
||||||
|
void* p = nullptr;
|
||||||
|
const void* cp = nullptr;
|
||||||
|
CHECK_ARG(cp, p);
|
||||||
|
CHECK_ARG_SIMPLE(cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(arg_test, volatile_pointer_arg) {
|
||||||
|
const void* p = nullptr;
|
||||||
|
volatile int* vip = nullptr;
|
||||||
|
const volatile int* cvip = nullptr;
|
||||||
|
CHECK_ARG(p, static_cast<volatile void*>(vip));
|
||||||
|
CHECK_ARG(p, static_cast<const volatile void*>(cvip));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct check_custom {
|
||||||
|
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
|
||||||
|
-> test_result {
|
||||||
|
struct test_buffer final : fmt::detail::buffer<char> {
|
||||||
|
char data[10];
|
||||||
|
test_buffer()
|
||||||
|
: fmt::detail::buffer<char>([](buffer<char>&, size_t) {}, data, 0,
|
||||||
|
10) {}
|
||||||
|
} buffer;
|
||||||
|
auto parse_ctx = fmt::format_parse_context("");
|
||||||
|
auto ctx = fmt::format_context(fmt::appender(buffer), fmt::format_args());
|
||||||
|
h.format(parse_ctx, ctx);
|
||||||
|
EXPECT_EQ(std::string(buffer.data, buffer.size()), "test");
|
||||||
|
return test_result();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(arg_test, custom_arg) {
|
||||||
|
auto test = test_struct();
|
||||||
|
using visitor =
|
||||||
|
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
|
||||||
|
auto&& v = testing::StrictMock<visitor>();
|
||||||
|
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
|
||||||
|
fmt::basic_format_arg<fmt::format_context>(test).visit(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(arg_test, visit_invalid_arg) {
|
||||||
|
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
|
||||||
|
EXPECT_CALL(visitor, visit(_));
|
||||||
|
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> class numeric_arg_test : public testing::Test {};
|
template <typename T> class numeric_arg_test : public testing::Test {};
|
||||||
|
|
||||||
|
#if FMT_BUILTIN_TYPES
|
||||||
using test_types =
|
using test_types =
|
||||||
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
|
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
|
||||||
unsigned, long, unsigned long, long long, unsigned long long,
|
unsigned, long, unsigned long, long long, unsigned long long,
|
||||||
float, double, long double>;
|
float, double, long double>;
|
||||||
|
#else
|
||||||
|
using test_types = testing::Types<int>;
|
||||||
|
#endif
|
||||||
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
||||||
|
|
||||||
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
@ -406,88 +472,34 @@ TYPED_TEST(numeric_arg_test, make_and_visit) {
|
|||||||
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
|
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(arg_test, char_arg) { CHECK_ARG(char, 'a', 'a'); }
|
|
||||||
|
|
||||||
TEST(arg_test, string_arg) {
|
|
||||||
char str_data[] = "test";
|
|
||||||
char* str = str_data;
|
|
||||||
const char* cstr = str;
|
|
||||||
CHECK_ARG(char, cstr, str);
|
|
||||||
|
|
||||||
auto sv = fmt::string_view(str);
|
|
||||||
CHECK_ARG(char, sv, std::string(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(arg_test, pointer_arg) {
|
|
||||||
void* p = nullptr;
|
|
||||||
const void* cp = nullptr;
|
|
||||||
CHECK_ARG(char, cp, p);
|
|
||||||
CHECK_ARG_SIMPLE(cp);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct check_custom {
|
|
||||||
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
|
|
||||||
-> test_result {
|
|
||||||
struct test_buffer final : fmt::detail::buffer<char> {
|
|
||||||
char data[10];
|
|
||||||
test_buffer()
|
|
||||||
: fmt::detail::buffer<char>([](buffer<char>&, size_t) {}, data, 0,
|
|
||||||
10) {}
|
|
||||||
} buffer;
|
|
||||||
auto parse_ctx = fmt::format_parse_context("");
|
|
||||||
auto ctx = fmt::format_context(fmt::appender(buffer), fmt::format_args());
|
|
||||||
h.format(parse_ctx, ctx);
|
|
||||||
EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
|
|
||||||
return test_result();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(arg_test, custom_arg) {
|
|
||||||
auto test = test_struct();
|
|
||||||
using visitor =
|
|
||||||
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
|
|
||||||
auto&& v = testing::StrictMock<visitor>();
|
|
||||||
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
|
|
||||||
fmt::detail::make_arg<fmt::format_context>(test).visit(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(arg_test, visit_invalid_arg) {
|
|
||||||
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
|
|
||||||
EXPECT_CALL(visitor, visit(_));
|
|
||||||
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_CONSTEXPR
|
#if FMT_USE_CONSTEXPR
|
||||||
|
|
||||||
enum class arg_id_result { none, empty, index, name };
|
enum class arg_id_result { none, index, name };
|
||||||
|
|
||||||
struct test_arg_id_handler {
|
struct test_arg_id_handler {
|
||||||
arg_id_result res = arg_id_result::none;
|
arg_id_result res = arg_id_result::none;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
string_view name;
|
fmt::string_view name;
|
||||||
|
|
||||||
constexpr void on_auto() { res = arg_id_result::empty; }
|
|
||||||
|
|
||||||
constexpr void on_index(int i) {
|
constexpr void on_index(int i) {
|
||||||
res = arg_id_result::index;
|
res = arg_id_result::index;
|
||||||
index = i;
|
index = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void on_name(string_view n) {
|
constexpr void on_name(fmt::string_view n) {
|
||||||
res = arg_id_result::name;
|
res = arg_id_result::name;
|
||||||
name = n;
|
name = n;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
|
constexpr auto parse_arg_id(const char (&s)[N]) -> test_arg_id_handler {
|
||||||
auto h = test_arg_id_handler();
|
auto h = test_arg_id_handler();
|
||||||
fmt::detail::parse_arg_id(s, s + N, h);
|
fmt::detail::parse_arg_id(s, s + N, h);
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(base_test, constexpr_parse_arg_id) {
|
TEST(base_test, constexpr_parse_arg_id) {
|
||||||
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
|
|
||||||
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
|
|
||||||
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
|
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
|
||||||
static_assert(parse_arg_id("42:").index == 42, "");
|
static_assert(parse_arg_id("42:").index == 42, "");
|
||||||
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
|
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
|
||||||
@ -504,19 +516,19 @@ template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(base_test, constexpr_parse_format_specs) {
|
TEST(base_test, constexpr_parse_format_specs) {
|
||||||
static_assert(parse_test_specs("<").align == fmt::align::left, "");
|
static_assert(parse_test_specs("<").align() == fmt::align::left, "");
|
||||||
static_assert(parse_test_specs("*^").fill.get<char>() == '*', "");
|
static_assert(parse_test_specs("*^").fill_unit<char>() == '*', "");
|
||||||
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
|
static_assert(parse_test_specs("+").sign() == fmt::sign::plus, "");
|
||||||
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
|
static_assert(parse_test_specs("-").sign() == fmt::sign::none, "");
|
||||||
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
|
static_assert(parse_test_specs(" ").sign() == fmt::sign::space, "");
|
||||||
static_assert(parse_test_specs("#").alt, "");
|
static_assert(parse_test_specs("#").alt(), "");
|
||||||
static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
|
static_assert(parse_test_specs("0").align() == fmt::align::numeric, "");
|
||||||
static_assert(parse_test_specs("L").localized, "");
|
static_assert(parse_test_specs("L").localized(), "");
|
||||||
static_assert(parse_test_specs("42").width == 42, "");
|
static_assert(parse_test_specs("42").width == 42, "");
|
||||||
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
|
static_assert(parse_test_specs("{42}").width_ref.index == 42, "");
|
||||||
static_assert(parse_test_specs(".42").precision == 42, "");
|
static_assert(parse_test_specs(".42").precision == 42, "");
|
||||||
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
|
static_assert(parse_test_specs(".{42}").precision_ref.index == 42, "");
|
||||||
static_assert(parse_test_specs("f").type == fmt::presentation_type::fixed,
|
static_assert(parse_test_specs("f").type() == fmt::presentation_type::fixed,
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,9 +551,9 @@ struct test_format_string_handler {
|
|||||||
bool error = false;
|
bool error = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
|
template <size_t N> constexpr auto parse_string(const char (&s)[N]) -> bool {
|
||||||
auto h = test_format_string_handler();
|
auto h = test_format_string_handler();
|
||||||
fmt::detail::parse_format_string<true>(fmt::string_view(s, N - 1), h);
|
fmt::detail::parse_format_string(fmt::string_view(s, N - 1), h);
|
||||||
return !h.error;
|
return !h.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,6 +565,7 @@ TEST(base_test, constexpr_parse_format_string) {
|
|||||||
static_assert(parse_string("{foo}"), "");
|
static_assert(parse_string("{foo}"), "");
|
||||||
static_assert(parse_string("{:}"), "");
|
static_assert(parse_string("{:}"), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_USE_CONSTEXPR
|
#endif // FMT_USE_CONSTEXPR
|
||||||
|
|
||||||
struct enabled_formatter {};
|
struct enabled_formatter {};
|
||||||
@ -584,15 +597,6 @@ template <> struct formatter<enabled_ptr_formatter*> {
|
|||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(base_test, has_formatter) {
|
|
||||||
using fmt::has_formatter;
|
|
||||||
using context = fmt::format_context;
|
|
||||||
static_assert(has_formatter<enabled_formatter, context>::value, "");
|
|
||||||
static_assert(!has_formatter<disabled_formatter, context>::value, "");
|
|
||||||
static_assert(!has_formatter<disabled_formatter_convertible, context>::value,
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct const_formattable {};
|
struct const_formattable {};
|
||||||
struct nonconst_formattable {};
|
struct nonconst_formattable {};
|
||||||
|
|
||||||
@ -644,51 +648,48 @@ FMT_END_NAMESPACE
|
|||||||
enum class unformattable_scoped_enum {};
|
enum class unformattable_scoped_enum {};
|
||||||
|
|
||||||
TEST(base_test, is_formattable) {
|
TEST(base_test, is_formattable) {
|
||||||
static_assert(!fmt::is_formattable<wchar_t>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<void>::value);
|
||||||
|
EXPECT_FALSE(fmt::is_formattable<wchar_t>::value);
|
||||||
#ifdef __cpp_char8_t
|
#ifdef __cpp_char8_t
|
||||||
static_assert(!fmt::is_formattable<char8_t>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<char8_t>::value);
|
||||||
#endif
|
#endif
|
||||||
static_assert(!fmt::is_formattable<char16_t>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<char16_t>::value);
|
||||||
static_assert(!fmt::is_formattable<char32_t>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<char32_t>::value);
|
||||||
static_assert(!fmt::is_formattable<signed char*>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<signed char*>::value);
|
||||||
static_assert(!fmt::is_formattable<unsigned char*>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<unsigned char*>::value);
|
||||||
static_assert(!fmt::is_formattable<const signed char*>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<const signed char*>::value);
|
||||||
static_assert(!fmt::is_formattable<const unsigned char*>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<const unsigned char*>::value);
|
||||||
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<const wchar_t*>::value);
|
||||||
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<const wchar_t[3]>::value);
|
||||||
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
|
EXPECT_FALSE(fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value);
|
||||||
"");
|
EXPECT_FALSE(fmt::is_formattable<enabled_ptr_formatter*>::value);
|
||||||
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
|
||||||
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<disabled_formatter_convertible>::value);
|
||||||
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
|
|
||||||
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
|
|
||||||
"");
|
|
||||||
|
|
||||||
static_assert(fmt::is_formattable<const_formattable&>::value, "");
|
EXPECT_TRUE(fmt::is_formattable<enabled_formatter>::value);
|
||||||
static_assert(fmt::is_formattable<const const_formattable&>::value, "");
|
EXPECT_TRUE(fmt::is_formattable<const_formattable&>::value);
|
||||||
|
EXPECT_TRUE(fmt::is_formattable<const const_formattable&>::value);
|
||||||
|
|
||||||
static_assert(fmt::is_formattable<nonconst_formattable&>::value, "");
|
EXPECT_TRUE(fmt::is_formattable<nonconst_formattable&>::value);
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
EXPECT_FALSE(fmt::is_formattable<const nonconst_formattable&>::value);
|
||||||
static_assert(!fmt::is_formattable<const nonconst_formattable&>::value, "");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static_assert(!fmt::is_formattable<convertible_to_pointer>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<convertible_to_pointer>::value);
|
||||||
const auto f = convertible_to_pointer_formattable();
|
const auto f = convertible_to_pointer_formattable();
|
||||||
auto str = std::string();
|
auto str = std::string();
|
||||||
fmt::format_to(std::back_inserter(str), "{}", f);
|
fmt::format_to(std::back_inserter(str), "{}", f);
|
||||||
EXPECT_EQ(str, "test");
|
EXPECT_EQ(str, "test");
|
||||||
|
|
||||||
static_assert(!fmt::is_formattable<void (*)()>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<void (*)()>::value);
|
||||||
|
|
||||||
struct s;
|
struct s;
|
||||||
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<int(s::*)>::value);
|
||||||
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<int (s::*)()>::value);
|
||||||
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<unformattable_scoped_enum>::value);
|
||||||
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
EXPECT_FALSE(fmt::is_formattable<unformattable_scoped_enum>::value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_CONCEPTS
|
#ifdef __cpp_concepts
|
||||||
TEST(base_test, formattable) {
|
TEST(base_test, formattable_concept) {
|
||||||
static_assert(fmt::formattable<char>);
|
static_assert(fmt::formattable<char>);
|
||||||
static_assert(fmt::formattable<char&>);
|
static_assert(fmt::formattable<char&>);
|
||||||
static_assert(fmt::formattable<char&&>);
|
static_assert(fmt::formattable<char&&>);
|
||||||
@ -709,56 +710,48 @@ TEST(base_test, format_to) {
|
|||||||
TEST(base_test, format_to_array) {
|
TEST(base_test, format_to_array) {
|
||||||
char buffer[4];
|
char buffer[4];
|
||||||
auto result = fmt::format_to(buffer, "{}", 12345);
|
auto result = fmt::format_to(buffer, "{}", 12345);
|
||||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||||
EXPECT_TRUE(result.truncated);
|
EXPECT_TRUE(result.truncated);
|
||||||
EXPECT_EQ(buffer + 4, result.out);
|
EXPECT_EQ(result.out, buffer + 4);
|
||||||
EXPECT_EQ("1234", fmt::string_view(buffer, 4));
|
EXPECT_EQ(fmt::string_view(buffer, 4), "1234");
|
||||||
|
|
||||||
char* out = nullptr;
|
char* out = nullptr;
|
||||||
EXPECT_THROW(out = result, std::runtime_error);
|
EXPECT_THROW(out = result, std::runtime_error);
|
||||||
(void)out;
|
(void)out;
|
||||||
|
|
||||||
result = fmt::format_to(buffer, "{:s}", "foobar");
|
result = fmt::format_to(buffer, "{:s}", "foobar");
|
||||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||||
EXPECT_TRUE(result.truncated);
|
EXPECT_TRUE(result.truncated);
|
||||||
EXPECT_EQ(buffer + 4, result.out);
|
EXPECT_EQ(result.out, buffer + 4);
|
||||||
EXPECT_EQ("foob", fmt::string_view(buffer, 4));
|
EXPECT_EQ(fmt::string_view(buffer, 4), "foob");
|
||||||
|
|
||||||
buffer[0] = 'x';
|
buffer[0] = 'x';
|
||||||
buffer[1] = 'x';
|
buffer[1] = 'x';
|
||||||
buffer[2] = 'x';
|
buffer[2] = 'x';
|
||||||
buffer[3] = 'x';
|
buffer[3] = 'x';
|
||||||
result = fmt::format_to(buffer, "{}", 'A');
|
result = fmt::format_to(buffer, "{}", 'A');
|
||||||
EXPECT_EQ(1, std::distance(&buffer[0], result.out));
|
EXPECT_EQ(std::distance(&buffer[0], result.out), 1);
|
||||||
EXPECT_FALSE(result.truncated);
|
EXPECT_FALSE(result.truncated);
|
||||||
EXPECT_EQ(buffer + 1, result.out);
|
EXPECT_EQ(result.out, buffer + 1);
|
||||||
EXPECT_EQ("Axxx", fmt::string_view(buffer, 4));
|
EXPECT_EQ(fmt::string_view(buffer, 4), "Axxx");
|
||||||
|
|
||||||
result = fmt::format_to(buffer, "{}{} ", 'B', 'C');
|
result = fmt::format_to(buffer, "{}{} ", 'B', 'C');
|
||||||
EXPECT_EQ(3, std::distance(&buffer[0], result.out));
|
EXPECT_EQ(std::distance(&buffer[0], result.out), 3);
|
||||||
EXPECT_FALSE(result.truncated);
|
EXPECT_FALSE(result.truncated);
|
||||||
EXPECT_EQ(buffer + 3, result.out);
|
EXPECT_EQ(result.out, buffer + 3);
|
||||||
EXPECT_EQ("BC x", fmt::string_view(buffer, 4));
|
EXPECT_EQ(fmt::string_view(buffer, 4), "BC x");
|
||||||
|
|
||||||
result = fmt::format_to(buffer, "{}", "ABCDE");
|
result = fmt::format_to(buffer, "{}", "ABCDE");
|
||||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||||
EXPECT_TRUE(result.truncated);
|
EXPECT_TRUE(result.truncated);
|
||||||
EXPECT_EQ("ABCD", fmt::string_view(buffer, 4));
|
EXPECT_EQ(fmt::string_view(buffer, 4), "ABCD");
|
||||||
|
|
||||||
result = fmt::format_to(buffer, "{}", std::string(1000, '*'));
|
result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str());
|
||||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||||
EXPECT_TRUE(result.truncated);
|
EXPECT_TRUE(result.truncated);
|
||||||
EXPECT_EQ("****", fmt::string_view(buffer, 4));
|
EXPECT_EQ(fmt::string_view(buffer, 4), "****");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cpp_lib_byte
|
|
||||||
TEST(base_test, format_byte) {
|
|
||||||
auto s = std::string();
|
|
||||||
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
|
|
||||||
EXPECT_EQ(s, "42");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Test that check is not found by ADL.
|
// Test that check is not found by ADL.
|
||||||
template <typename T> void check(T);
|
template <typename T> void check(T);
|
||||||
TEST(base_test, adl_check) {
|
TEST(base_test, adl_check) {
|
||||||
@ -776,19 +769,6 @@ TEST(base_test, no_implicit_conversion_to_string_view) {
|
|||||||
fmt::is_formattable<implicitly_convertible_to_string_view>::value);
|
fmt::is_formattable<implicitly_convertible_to_string_view>::value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FMT_USE_STRING_VIEW
|
|
||||||
struct implicitly_convertible_to_std_string_view {
|
|
||||||
operator std::string_view() const { return "foo"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(base_test, no_implicit_conversion_to_std_string_view) {
|
|
||||||
EXPECT_FALSE(
|
|
||||||
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// std::is_constructible is broken in MSVC until version 2015.
|
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
|
|
||||||
struct explicitly_convertible_to_string_view {
|
struct explicitly_convertible_to_string_view {
|
||||||
explicit operator fmt::string_view() const { return "foo"; }
|
explicit operator fmt::string_view() const { return "foo"; }
|
||||||
};
|
};
|
||||||
@ -800,7 +780,16 @@ TEST(base_test, format_explicitly_convertible_to_string_view) {
|
|||||||
!fmt::is_formattable<explicitly_convertible_to_string_view>::value, "");
|
!fmt::is_formattable<explicitly_convertible_to_string_view>::value, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef FMT_USE_STRING_VIEW
|
#if FMT_CPLUSPLUS >= 201703L
|
||||||
|
struct implicitly_convertible_to_std_string_view {
|
||||||
|
operator std::string_view() const { return "foo"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(base_test, no_implicit_conversion_to_std_string_view) {
|
||||||
|
EXPECT_FALSE(
|
||||||
|
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
|
||||||
|
}
|
||||||
|
|
||||||
struct explicitly_convertible_to_std_string_view {
|
struct explicitly_convertible_to_std_string_view {
|
||||||
explicit operator std::string_view() const { return "foo"; }
|
explicit operator std::string_view() const { return "foo"; }
|
||||||
};
|
};
|
||||||
@ -812,14 +801,12 @@ TEST(base_test, format_explicitly_convertible_to_std_string_view) {
|
|||||||
!fmt::is_formattable<explicitly_convertible_to_std_string_view>::value,
|
!fmt::is_formattable<explicitly_convertible_to_std_string_view>::value,
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
# endif
|
#endif // FMT_CPLUSPLUS >= 201703L
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(base_test, has_const_formatter) {
|
TEST(base_test, has_formatter) {
|
||||||
EXPECT_TRUE((fmt::detail::has_const_formatter<const_formattable,
|
EXPECT_TRUE((fmt::detail::has_formatter<const const_formattable, char>()));
|
||||||
fmt::format_context>()));
|
EXPECT_FALSE(
|
||||||
EXPECT_FALSE((fmt::detail::has_const_formatter<nonconst_formattable,
|
(fmt::detail::has_formatter<const nonconst_formattable, char>()));
|
||||||
fmt::format_context>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(base_test, format_nonconst) {
|
TEST(base_test, format_nonconst) {
|
||||||
@ -829,7 +816,7 @@ TEST(base_test, format_nonconst) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(base_test, throw_in_buffer_dtor) {
|
TEST(base_test, throw_in_buffer_dtor) {
|
||||||
enum { buffer_size = 256 };
|
constexpr int buffer_size = 256;
|
||||||
|
|
||||||
struct throwing_iterator {
|
struct throwing_iterator {
|
||||||
int& count;
|
int& count;
|
||||||
@ -851,7 +838,7 @@ TEST(base_test, throw_in_buffer_dtor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct its_a_trap {
|
struct convertible_to_any_type_with_member_x {
|
||||||
template <typename T> operator T() const {
|
template <typename T> operator T() const {
|
||||||
auto v = T();
|
auto v = T();
|
||||||
v.x = 42;
|
v.x = 42;
|
||||||
@ -860,12 +847,12 @@ struct its_a_trap {
|
|||||||
};
|
};
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct formatter<its_a_trap> {
|
template <> struct formatter<convertible_to_any_type_with_member_x> {
|
||||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto format(its_a_trap, format_context& ctx) const
|
auto format(convertible_to_any_type_with_member_x, format_context& ctx) const
|
||||||
-> decltype(ctx.out()) const {
|
-> decltype(ctx.out()) const {
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
*out++ = 'x';
|
*out++ = 'x';
|
||||||
@ -874,8 +861,51 @@ template <> struct formatter<its_a_trap> {
|
|||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(base_test, trappy_conversion) {
|
TEST(base_test, promiscuous_conversions) {
|
||||||
auto s = std::string();
|
auto s = std::string();
|
||||||
fmt::format_to(std::back_inserter(s), "{}", its_a_trap());
|
fmt::format_to(std::back_inserter(s), "{}",
|
||||||
|
convertible_to_any_type_with_member_x());
|
||||||
EXPECT_EQ(s, "x");
|
EXPECT_EQ(s, "x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct custom_container {
|
||||||
|
char data;
|
||||||
|
|
||||||
|
using value_type = char;
|
||||||
|
|
||||||
|
auto size() const -> size_t { return 0; }
|
||||||
|
void resize(size_t) {}
|
||||||
|
|
||||||
|
void push_back(char) {}
|
||||||
|
auto operator[](size_t) -> char& { return data; }
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct is_contiguous<custom_container> : std::true_type {};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(base_test, format_to_custom_container) {
|
||||||
|
auto c = custom_container();
|
||||||
|
fmt::format_to(std::back_inserter(c), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base_test, no_repeated_format_string_conversions) {
|
||||||
|
struct nondeterministic_format_string {
|
||||||
|
mutable int i = 0;
|
||||||
|
FMT_CONSTEXPR operator fmt::string_view() const {
|
||||||
|
return {"{}", i++ != 0 ? 2u : 0u};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200
|
||||||
|
char buf[10];
|
||||||
|
fmt::format_to(buf, nondeterministic_format_string());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base_test, format_context_accessors) {
|
||||||
|
auto copy = [](fmt::appender app, const fmt::format_context& ctx) {
|
||||||
|
return fmt::format_context(app, ctx.args(), ctx.locale());
|
||||||
|
};
|
||||||
|
fmt::detail::ignore_unused(copy);
|
||||||
|
}
|
||||||
|
|||||||
@ -15,11 +15,9 @@
|
|||||||
#include "util.h" // get_locale
|
#include "util.h" // get_locale
|
||||||
|
|
||||||
using fmt::runtime;
|
using fmt::runtime;
|
||||||
|
using fmt::sys_time;
|
||||||
using testing::Contains;
|
using testing::Contains;
|
||||||
|
|
||||||
template <typename Duration>
|
|
||||||
using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
|
|
||||||
|
|
||||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||||
# define FMT_HAS_C99_STRFTIME 0
|
# define FMT_HAS_C99_STRFTIME 0
|
||||||
@ -57,8 +55,8 @@ auto make_second(int s) -> std::tm {
|
|||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string system_strftime(const std::string& format, const std::tm* timeptr,
|
auto system_strftime(const std::string& format, const std::tm* timeptr,
|
||||||
std::locale* locptr = nullptr) {
|
std::locale* locptr = nullptr) -> std::string {
|
||||||
auto loc = locptr ? *locptr : std::locale::classic();
|
auto loc = locptr ? *locptr : std::locale::classic();
|
||||||
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
@ -75,8 +73,8 @@ std::string system_strftime(const std::string& format, const std::tm* timeptr,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
|
FMT_CONSTEXPR auto make_tm(int year, int mon, int mday, int hour, int min,
|
||||||
int sec) {
|
int sec) -> std::tm {
|
||||||
auto tm = std::tm();
|
auto tm = std::tm();
|
||||||
tm.tm_sec = sec;
|
tm.tm_sec = sec;
|
||||||
tm.tm_min = min;
|
tm.tm_min = min;
|
||||||
@ -240,196 +238,145 @@ TEST(chrono_test, format_to_empty_container) {
|
|||||||
EXPECT_EQ(s, "42");
|
EXPECT_EQ(s, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
|
|
||||||
|
|
||||||
auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
|
|
||||||
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
|
|
||||||
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
|
|
||||||
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
|
|
||||||
lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday &&
|
|
||||||
lhs.tm_isdst == rhs.tm_isdst;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(chrono_test, gmtime) {
|
TEST(chrono_test, gmtime) {
|
||||||
auto t = std::time(nullptr);
|
auto t = std::time(nullptr);
|
||||||
auto tm = *std::gmtime(&t);
|
auto expected = *std::gmtime(&t);
|
||||||
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
auto actual = fmt::gmtime(t);
|
||||||
|
EXPECT_EQ(actual.tm_sec, expected.tm_sec);
|
||||||
|
EXPECT_EQ(actual.tm_min, expected.tm_min);
|
||||||
|
EXPECT_EQ(actual.tm_hour, expected.tm_hour);
|
||||||
|
EXPECT_EQ(actual.tm_mday, expected.tm_mday);
|
||||||
|
EXPECT_EQ(actual.tm_mon, expected.tm_mon);
|
||||||
|
EXPECT_EQ(actual.tm_year, expected.tm_year);
|
||||||
|
EXPECT_EQ(actual.tm_wday, expected.tm_wday);
|
||||||
|
EXPECT_EQ(actual.tm_yday, expected.tm_yday);
|
||||||
|
EXPECT_EQ(actual.tm_isdst, expected.tm_isdst);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TimePoint>
|
template <typename Time> void test_time(Time time) {
|
||||||
auto strftime_full_utc(TimePoint tp) -> std::string {
|
EXPECT_EQ(fmt::format("{}", time), "1979-03-12 12:00:00");
|
||||||
auto t = std::chrono::system_clock::to_time_t(tp);
|
EXPECT_EQ(fmt::format("{:}", time), "1979-03-12 12:00:00");
|
||||||
auto tm = *std::gmtime(&t);
|
|
||||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
EXPECT_EQ(fmt::format("{:%%}", time), "%");
|
||||||
|
EXPECT_EQ(fmt::format("{:%n}", time), "\n");
|
||||||
|
EXPECT_EQ(fmt::format("{:%t}", time), "\t");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Y}", time), "1979");
|
||||||
|
EXPECT_EQ(fmt::format("{:%EY}", time), "1979");
|
||||||
|
EXPECT_EQ(fmt::format("{:%y}", time), "79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Oy}", time), "79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Ey}", time), "79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%C}", time), "19");
|
||||||
|
EXPECT_EQ(fmt::format("{:%EC}", time), "19");
|
||||||
|
EXPECT_EQ(fmt::format("{:%G}", time), "1979");
|
||||||
|
EXPECT_EQ(fmt::format("{:%g}", time), "79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%b}", time), "Mar");
|
||||||
|
EXPECT_EQ(fmt::format("{:%h}", time), "Mar");
|
||||||
|
EXPECT_EQ(fmt::format("{:%B}", time), "March");
|
||||||
|
EXPECT_EQ(fmt::format("{:%m}", time), "03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Om}", time), "03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%U}", time), "10");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OU}", time), "10");
|
||||||
|
EXPECT_EQ(fmt::format("{:%W}", time), "11");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OW}", time), "11");
|
||||||
|
EXPECT_EQ(fmt::format("{:%V}", time), "11");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OV}", time), "11");
|
||||||
|
EXPECT_EQ(fmt::format("{:%j}", time), "071");
|
||||||
|
EXPECT_EQ(fmt::format("{:%d}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Od}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%e}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Oe}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%a}", time), "Mon");
|
||||||
|
EXPECT_EQ(fmt::format("{:%A}", time), "Monday");
|
||||||
|
EXPECT_EQ(fmt::format("{:%w}", time), "1");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Ow}", time), "1");
|
||||||
|
EXPECT_EQ(fmt::format("{:%u}", time), "1");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Ou}", time), "1");
|
||||||
|
EXPECT_EQ(fmt::format("{:%H}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OH}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%I}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OI}", time), "12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%M}", time), "00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OM}", time), "00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", time), "00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%OS}", time), "00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%x}", time), "03/12/79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Ex}", time), "03/12/79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%X}", time), "12:00:00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%EX}", time), "12:00:00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%D}", time), "03/12/79");
|
||||||
|
EXPECT_EQ(fmt::format("{:%F}", time), "1979-03-12");
|
||||||
|
EXPECT_EQ(fmt::format("{:%R}", time), "12:00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%T}", time), "12:00:00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%p}", time), "PM");
|
||||||
|
EXPECT_EQ(fmt::format("{:%c}", time), "Mon Mar 12 12:00:00 1979");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Ec}", time), "Mon Mar 12 12:00:00 1979");
|
||||||
|
EXPECT_EQ(fmt::format("{:%r}", time), "12:00:00 PM");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", time), "1979-03-12 12:00:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, system_clock_time_point) {
|
TEST(chrono_test, sys_time) {
|
||||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
auto time =
|
||||||
std::chrono::system_clock::now());
|
fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
|
||||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
test_time(time);
|
||||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
|
EXPECT_EQ(fmt::format("{:%z}", time), "+0000");
|
||||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
|
EXPECT_EQ(fmt::format("{:%Ez}", time), "+00:00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Oz}", time), "+00:00");
|
||||||
|
EXPECT_EQ(fmt::format("{:%Z}", time), "UTC");
|
||||||
|
}
|
||||||
|
|
||||||
auto t2 = sys_time<std::chrono::seconds>(std::chrono::seconds(42));
|
TEST(chrono_test, local_time) {
|
||||||
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
auto time =
|
||||||
|
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(290088000));
|
||||||
|
test_time(time);
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
|
||||||
|
fmt::format_error, "no timezone");
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
|
||||||
|
fmt::format_error, "no timezone");
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> spec_list = {
|
template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
|
||||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
auto set_tm_gmtoff(T& time, long offset) -> bool {
|
||||||
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
time.tm_gmtoff = offset;
|
||||||
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
return true;
|
||||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
}
|
||||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
|
||||||
"%EX", "%D", "%F", "%R", "%T", "%p"};
|
auto set_tm_gmtoff(T&, long) -> bool {
|
||||||
#ifndef _WIN32
|
return false;
|
||||||
// Disabled on Windows because these formats are not consistent among
|
}
|
||||||
// platforms.
|
|
||||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
|
||||||
#elif !FMT_HAS_C99_STRFTIME
|
|
||||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
|
||||||
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d",
|
|
||||||
"%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"};
|
|
||||||
#endif
|
|
||||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
|
||||||
|
|
||||||
for (const auto& spec : spec_list) {
|
TEST(chrono_test, tm) {
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
auto time = fmt::gmtime(290088000);
|
||||||
auto tm = *std::gmtime(&t);
|
test_time(time);
|
||||||
|
if (set_tm_gmtoff(time, -28800)) {
|
||||||
auto sys_output = system_strftime(spec, &tm);
|
EXPECT_EQ(fmt::format(fmt::runtime("{:%z}"), time), "-0800");
|
||||||
|
EXPECT_EQ(fmt::format(fmt::runtime("{:%Ez}"), time), "-08:00");
|
||||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
EXPECT_EQ(fmt::format(fmt::runtime("{:%Oz}"), time), "-08:00");
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
} else {
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
|
||||||
|
fmt::format_error, "no timezone");
|
||||||
}
|
}
|
||||||
|
char tz[] = "EET";
|
||||||
// Timezone formatters tests makes sense for localtime.
|
if (fmt::detail::set_tm_zone(time, tz)) {
|
||||||
#if FMT_HAS_C99_STRFTIME
|
EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET");
|
||||||
spec_list = {"%z", "%Z"};
|
} else {
|
||||||
#else
|
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
|
||||||
spec_list = {"%Z"};
|
fmt::format_error, "no timezone");
|
||||||
#endif
|
|
||||||
for (const auto& spec : spec_list) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
|
|
||||||
auto sys_output = system_strftime(spec, &tm);
|
|
||||||
|
|
||||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
|
||||||
|
|
||||||
if (spec == "%z") {
|
|
||||||
sys_output.insert(sys_output.end() - 2, 1, ':');
|
|
||||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
|
||||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
|
||||||
// the timezone in std::tm (if it presents on platform).
|
|
||||||
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::gmtime(&t);
|
|
||||||
|
|
||||||
std::vector<std::string> tz_names = {"GMT", "UTC"};
|
|
||||||
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
|
|
||||||
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::gmtime(&t);
|
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{:%z}", t1), "+0000");
|
|
||||||
EXPECT_EQ(fmt::format("{:%z}", tm), "+0000");
|
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{:%Ez}", t1), "+00:00");
|
|
||||||
EXPECT_EQ(fmt::format("{:%Ez}", tm), "+00:00");
|
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{:%Oz}", t1), "+00:00");
|
|
||||||
EXPECT_EQ(fmt::format("{:%Oz}", tm), "+00:00");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_LOCAL_TIME
|
TEST(chrono_test, daylight_savings_time_end) {
|
||||||
|
// 2024-10-27 03:05 as the number of seconds since epoch in Europe/Kyiv time.
|
||||||
TEST(chrono_test, localtime) {
|
// It is slightly after the DST end and passing it to to_sys will result in
|
||||||
auto t = std::time(nullptr);
|
// an ambiguous time error:
|
||||||
auto tm = *std::localtime(&t);
|
// 2024-10-27 03:05:00 is ambiguous. It could be
|
||||||
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
// 2024-10-27 03:05:00 EEST == 2024-10-27 00:05:00 UTC or
|
||||||
|
// 2024-10-27 03:05:00 EET == 2024-10-27 01:05:00 UTC
|
||||||
|
auto t =
|
||||||
|
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(1729998300));
|
||||||
|
EXPECT_EQ(fmt::format("{}", t), "2024-10-27 03:05:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Duration>
|
|
||||||
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(
|
|
||||||
std::chrono::current_zone()->to_sys(tp));
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(chrono_test, local_system_clock_time_point) {
|
|
||||||
# ifdef _WIN32
|
|
||||||
return; // Not supported on Windows.
|
|
||||||
# endif
|
|
||||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
|
||||||
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
|
|
||||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
|
||||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
|
|
||||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
|
|
||||||
using time_point = std::chrono::local_time<std::chrono::seconds>;
|
|
||||||
auto t2 = time_point(std::chrono::seconds(86400 + 42));
|
|
||||||
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
|
||||||
|
|
||||||
std::vector<std::string> spec_list = {
|
|
||||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
|
||||||
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
|
||||||
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
|
||||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
|
||||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
|
||||||
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
|
|
||||||
# ifndef _WIN32
|
|
||||||
// Disabled on Windows because these formats are not consistent among
|
|
||||||
// platforms.
|
|
||||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
|
||||||
# elif !FMT_HAS_C99_STRFTIME
|
|
||||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
|
||||||
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
|
|
||||||
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
|
|
||||||
# endif
|
|
||||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
|
||||||
|
|
||||||
for (const auto& spec : spec_list) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(
|
|
||||||
std::chrono::current_zone()->to_sys(t1));
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
|
|
||||||
auto sys_output = system_strftime(spec, &tm);
|
|
||||||
|
|
||||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
|
|
||||||
spec_list.cend()) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(
|
|
||||||
std::chrono::current_zone()->to_sys(t1));
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
|
|
||||||
auto sys_output = system_strftime("%z", &tm);
|
|
||||||
sys_output.insert(sys_output.end() - 2, 1, ':');
|
|
||||||
|
|
||||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
|
|
||||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
|
||||||
|
|
||||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
|
|
||||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // FMT_USE_LOCAL_TIME
|
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
|
||||||
|
|
||||||
TEST(chrono_test, format_default) {
|
TEST(chrono_test, format_default) {
|
||||||
EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
|
EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
|
||||||
EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)),
|
EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)),
|
||||||
@ -539,6 +486,7 @@ TEST(chrono_test, format_specs) {
|
|||||||
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12");
|
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12");
|
||||||
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04");
|
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04");
|
||||||
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02");
|
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02");
|
||||||
|
EXPECT_EQ(fmt::format("{:%j}", days(12)), "12");
|
||||||
EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345");
|
EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345");
|
||||||
EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345");
|
EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345");
|
||||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)),
|
EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)),
|
||||||
@ -605,12 +553,12 @@ auto format_tm(const std::tm& time, fmt::string_view spec,
|
|||||||
TEST(chrono_test, locale) {
|
TEST(chrono_test, locale) {
|
||||||
auto loc = get_locale("ja_JP.utf8");
|
auto loc = get_locale("ja_JP.utf8");
|
||||||
if (loc == std::locale::classic()) return;
|
if (loc == std::locale::classic()) return;
|
||||||
# define EXPECT_TIME(spec, time, duration) \
|
#define EXPECT_TIME(spec, time, duration) \
|
||||||
{ \
|
{ \
|
||||||
auto jp_loc = std::locale("ja_JP.utf8"); \
|
auto jp_loc = std::locale("ja_JP.utf8"); \
|
||||||
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
||||||
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
||||||
}
|
}
|
||||||
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
||||||
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
||||||
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
||||||
@ -753,7 +701,7 @@ TEST(chrono_test, weekday) {
|
|||||||
std::locale::global(loc);
|
std::locale::global(loc);
|
||||||
|
|
||||||
auto sat = fmt::weekday(6);
|
auto sat = fmt::weekday(6);
|
||||||
|
|
||||||
auto tm = std::tm();
|
auto tm = std::tm();
|
||||||
tm.tm_wday = static_cast<int>(sat.c_encoding());
|
tm.tm_wday = static_cast<int>(sat.c_encoding());
|
||||||
|
|
||||||
@ -763,10 +711,10 @@ TEST(chrono_test, weekday) {
|
|||||||
EXPECT_EQ(fmt::format("{:%a}", tm), "Sat");
|
EXPECT_EQ(fmt::format("{:%a}", tm), "Sat");
|
||||||
|
|
||||||
if (loc != std::locale::classic()) {
|
if (loc != std::locale::classic()) {
|
||||||
auto saturdays = std::vector<std::string>{"sáb", "sá."};
|
auto saturdays = std::vector<std::string>{"sáb", "sá.", "sáb."};
|
||||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
|
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
|
||||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat)));
|
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat)));
|
||||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm)));
|
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,20 +743,14 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
|||||||
"01.234000");
|
"01.234000");
|
||||||
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
|
||||||
"-01.234000");
|
"-01.234000");
|
||||||
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}),
|
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}), "12.34");
|
||||||
"12.34");
|
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}), "12.37");
|
||||||
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}),
|
|
||||||
"12.37");
|
|
||||||
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{-12375}),
|
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{-12375}),
|
||||||
"-12.37");
|
"-12.37");
|
||||||
EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}),
|
EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}), "12");
|
||||||
"12");
|
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}), "39.99");
|
||||||
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}),
|
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}), "01.00");
|
||||||
"39.99");
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}), "00.001");
|
||||||
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}),
|
|
||||||
"01.00");
|
|
||||||
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}),
|
|
||||||
"00.001");
|
|
||||||
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
|
||||||
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
|
||||||
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
|
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
|
||||||
@ -822,11 +764,11 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
|||||||
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
||||||
}
|
}
|
||||||
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
||||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(-123456789)), "-00.123456789");
|
||||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789");
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(9123456789)), "09.123456789");
|
||||||
// Verify that only the seconds part is extracted and printed.
|
// Verify that only the seconds part is extracted and printed.
|
||||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789");
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123456789)), "39.123456789");
|
||||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000");
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123000000)), "39.123000000");
|
||||||
{
|
{
|
||||||
// Now the hour is printed, and we also test if negative doubles work.
|
// Now the hour is printed, and we also test if negative doubles work.
|
||||||
auto dur = nanoseconds_dbl{-99123456789};
|
auto dur = nanoseconds_dbl{-99123456789};
|
||||||
@ -837,7 +779,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
|||||||
}
|
}
|
||||||
// Check that durations with precision greater than std::chrono::seconds have
|
// Check that durations with precision greater than std::chrono::seconds have
|
||||||
// fixed precision, and print zeros even if there is no fractional part.
|
// fixed precision, and print zeros even if there is no fractional part.
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds(7000000)),
|
||||||
"07.000000");
|
"07.000000");
|
||||||
EXPECT_EQ(fmt::format("{:%S}",
|
EXPECT_EQ(fmt::format("{:%S}",
|
||||||
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
||||||
@ -857,14 +799,12 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
|||||||
"-05:27.68");
|
"-05:27.68");
|
||||||
|
|
||||||
// Check that floating point seconds with ratio<1,1> are printed.
|
// Check that floating point seconds with ratio<1,1> are printed.
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1.5)),
|
||||||
"01.500000");
|
"01.500000");
|
||||||
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
|
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-61.25)),
|
||||||
"-01:01.250000");
|
"-01:01.250000");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
|
||||||
|
|
||||||
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
|
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
|
||||||
// (time zone database) is not shipped with many windows versions.
|
// (time zone database) is not shipped with many windows versions.
|
||||||
#if FMT_USE_UTC_TIME && !defined(_WIN32)
|
#if FMT_USE_UTC_TIME && !defined(_WIN32)
|
||||||
@ -937,19 +877,11 @@ TEST(chrono_test, timestamp_sub_seconds) {
|
|||||||
auto t8 =
|
auto t8 =
|
||||||
sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789));
|
sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789));
|
||||||
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
|
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
|
||||||
|
EXPECT_EQ(fmt::format("{:%T}", t8), "00:00:00.123456789");
|
||||||
|
|
||||||
auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
auto t9 =
|
||||||
std::chrono::system_clock::now());
|
|
||||||
auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
|
|
||||||
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
|
|
||||||
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
|
||||||
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
|
|
||||||
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
|
||||||
fmt::format("{:%Y-%m-%d %T}", t9));
|
|
||||||
|
|
||||||
auto t10 =
|
|
||||||
sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
|
sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
|
||||||
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
|
EXPECT_EQ(fmt::format("{:%S}", t9), "02.000");
|
||||||
|
|
||||||
auto epoch = sys_time<std::chrono::milliseconds>();
|
auto epoch = sys_time<std::chrono::milliseconds>();
|
||||||
auto d = std::chrono::milliseconds(250);
|
auto d = std::chrono::milliseconds(250);
|
||||||
@ -1011,6 +943,10 @@ TEST(chrono_test, glibc_extensions) {
|
|||||||
EXPECT_EQ(fmt::format("{:%U,%W,%V}", t), "02,01,01");
|
EXPECT_EQ(fmt::format("{:%U,%W,%V}", t), "02,01,01");
|
||||||
EXPECT_EQ(fmt::format("{:%_U,%_W,%_V}", t), " 2, 1, 1");
|
EXPECT_EQ(fmt::format("{:%_U,%_W,%_V}", t), " 2, 1, 1");
|
||||||
EXPECT_EQ(fmt::format("{:%-U,%-W,%-V}", t), "2,1,1");
|
EXPECT_EQ(fmt::format("{:%-U,%-W,%-V}", t), "2,1,1");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%j}", t), "008");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_j}", t), " 8");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-j}", t), "8");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -1022,6 +958,30 @@ TEST(chrono_test, glibc_extensions) {
|
|||||||
|
|
||||||
EXPECT_EQ(fmt::format("{:%e}", t), " 7");
|
EXPECT_EQ(fmt::format("{:%e}", t), " 7");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = std::tm();
|
||||||
|
t.tm_year = 7 - 1900;
|
||||||
|
EXPECT_EQ(fmt::format("{:%Y}", t), "0007");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_Y}", t), " 7");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-Y}", t), "7");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = std::tm();
|
||||||
|
t.tm_year = -5 - 1900;
|
||||||
|
EXPECT_EQ(fmt::format("{:%Y}", t), "-005");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_Y}", t), " -5");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-Y}", t), "-5");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto t = std::tm();
|
||||||
|
t.tm_mon = 7 - 1;
|
||||||
|
EXPECT_EQ(fmt::format("{:%m}", t), "07");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_m}", t), " 7");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-m}", t), "7");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, out_of_range) {
|
TEST(chrono_test, out_of_range) {
|
||||||
@ -1032,7 +992,7 @@ TEST(chrono_test, out_of_range) {
|
|||||||
TEST(chrono_test, year_month_day) {
|
TEST(chrono_test, year_month_day) {
|
||||||
auto loc = get_locale("es_ES.UTF-8");
|
auto loc = get_locale("es_ES.UTF-8");
|
||||||
std::locale::global(loc);
|
std::locale::global(loc);
|
||||||
|
|
||||||
auto year = fmt::year(2024);
|
auto year = fmt::year(2024);
|
||||||
auto month = fmt::month(1);
|
auto month = fmt::month(1);
|
||||||
auto day = fmt::day(1);
|
auto day = fmt::day(1);
|
||||||
@ -1058,6 +1018,6 @@ TEST(chrono_test, year_month_day) {
|
|||||||
if (loc != std::locale::classic()) {
|
if (loc != std::locale::classic()) {
|
||||||
auto months = std::vector<std::string>{"ene.", "ene"};
|
auto months = std::vector<std::string>{"ene.", "ene"};
|
||||||
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month)));
|
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month)));
|
||||||
EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month)));
|
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,68 @@
|
|||||||
|
|
||||||
#include <iterator> // std::back_inserter
|
#include <iterator> // std::back_inserter
|
||||||
|
|
||||||
#include "gtest-extra.h" // EXPECT_WRITE
|
#include "gtest-extra.h" // EXPECT_WRITE, EXPECT_THROW_MSG
|
||||||
|
|
||||||
|
TEST(color_test, text_style) {
|
||||||
|
EXPECT_FALSE(fmt::text_style().has_foreground());
|
||||||
|
EXPECT_FALSE(fmt::text_style().has_background());
|
||||||
|
EXPECT_FALSE(fmt::text_style().has_emphasis());
|
||||||
|
|
||||||
|
EXPECT_TRUE(fg(fmt::rgb(0)).has_foreground());
|
||||||
|
EXPECT_FALSE(fg(fmt::rgb(0)).has_background());
|
||||||
|
EXPECT_FALSE(fg(fmt::rgb(0)).has_emphasis());
|
||||||
|
EXPECT_TRUE(bg(fmt::rgb(0)).has_background());
|
||||||
|
EXPECT_FALSE(bg(fmt::rgb(0)).has_foreground());
|
||||||
|
EXPECT_FALSE(bg(fmt::rgb(0)).has_emphasis());
|
||||||
|
|
||||||
|
EXPECT_TRUE(
|
||||||
|
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_foreground());
|
||||||
|
EXPECT_TRUE(
|
||||||
|
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_background());
|
||||||
|
EXPECT_FALSE(
|
||||||
|
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_emphasis());
|
||||||
|
|
||||||
|
EXPECT_EQ(fg(fmt::rgb(0x000000)) | fg(fmt::rgb(0x000000)),
|
||||||
|
fg(fmt::rgb(0x000000)));
|
||||||
|
EXPECT_EQ(fg(fmt::rgb(0x00000F)) | fg(fmt::rgb(0x00000F)),
|
||||||
|
fg(fmt::rgb(0x00000F)));
|
||||||
|
EXPECT_EQ(fg(fmt::rgb(0xC0F000)) | fg(fmt::rgb(0x000FEE)),
|
||||||
|
fg(fmt::rgb(0xC0FFEE)));
|
||||||
|
|
||||||
|
EXPECT_THROW_MSG(
|
||||||
|
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::black),
|
||||||
|
fmt::format_error, "can't OR a terminal color");
|
||||||
|
EXPECT_THROW_MSG(
|
||||||
|
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::white),
|
||||||
|
fmt::format_error, "can't OR a terminal color");
|
||||||
|
EXPECT_THROW_MSG(
|
||||||
|
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::black),
|
||||||
|
fmt::format_error, "can't OR a terminal color");
|
||||||
|
EXPECT_THROW_MSG(
|
||||||
|
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::white),
|
||||||
|
fmt::format_error, "can't OR a terminal color");
|
||||||
|
EXPECT_THROW_MSG(fg(fmt::terminal_color::black) | fg(fmt::color::black),
|
||||||
|
fmt::format_error, "can't OR a terminal color");
|
||||||
|
EXPECT_THROW_MSG(bg(fmt::terminal_color::black) | bg(fmt::color::black),
|
||||||
|
fmt::format_error, "can't OR a terminal color");
|
||||||
|
|
||||||
|
EXPECT_NO_THROW(fg(fmt::terminal_color::white) |
|
||||||
|
bg(fmt::terminal_color::white));
|
||||||
|
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | bg(fmt::rgb(0xFFFFFF)));
|
||||||
|
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | fmt::text_style());
|
||||||
|
EXPECT_NO_THROW(bg(fmt::terminal_color::white) | fmt::text_style());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(color_test, format) {
|
TEST(color_test, format) {
|
||||||
|
EXPECT_EQ(fmt::format(fmt::text_style(), "no style"), "no style");
|
||||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||||
|
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 0, 0)) | fg(fmt::rgb(0, 20, 30)),
|
||||||
|
"rgb(255,20,30)"),
|
||||||
|
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format(fg(fmt::rgb(0, 0, 0)) | fg(fmt::rgb(0, 0, 0)), "rgb(0,0,0)"),
|
||||||
|
"\x1b[38;2;000;000;000mrgb(0,0,0)\x1b[0m");
|
||||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
||||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
@ -56,6 +113,15 @@ TEST(color_test, format) {
|
|||||||
EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) |
|
EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) |
|
||||||
fmt::emphasis::underline)),
|
fmt::emphasis::underline)),
|
||||||
"\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m");
|
"\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format(
|
||||||
|
"{}", fmt::styled(
|
||||||
|
"all", fmt::emphasis::bold | fmt::emphasis::faint |
|
||||||
|
fmt::emphasis::italic |
|
||||||
|
fmt::emphasis::underline | fmt::emphasis::blink |
|
||||||
|
fmt::emphasis::reverse | fmt::emphasis::conceal |
|
||||||
|
fmt::emphasis::strikethrough)),
|
||||||
|
"\x1b[1;2;3;4;5;7;8;9mall\x1b[0m");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(color_test, format_to) {
|
TEST(color_test, format_to) {
|
||||||
|
|||||||
@ -115,8 +115,7 @@ function (run_tests)
|
|||||||
endif ()
|
endif ()
|
||||||
endfunction ()
|
endfunction ()
|
||||||
|
|
||||||
|
# Check if the source file skeleton compiles.
|
||||||
# check if the source file skeleton compiles
|
|
||||||
expect_compile(check "")
|
expect_compile(check "")
|
||||||
expect_compile(check-error "compilation_error" ERROR)
|
expect_compile(check-error "compilation_error" ERROR)
|
||||||
|
|
||||||
@ -166,31 +165,6 @@ expect_compile(format-lots-of-arguments-with-function "
|
|||||||
fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f);
|
fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f);
|
||||||
" ERROR)
|
" ERROR)
|
||||||
|
|
||||||
# Check if user-defined literals are available
|
|
||||||
include(CheckCXXSourceCompiles)
|
|
||||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
|
|
||||||
check_cxx_source_compiles("
|
|
||||||
void operator\"\" _udl(long double);
|
|
||||||
int main() {}"
|
|
||||||
SUPPORTS_USER_DEFINED_LITERALS)
|
|
||||||
set(CMAKE_REQUIRED_FLAGS )
|
|
||||||
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
|
|
||||||
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Make sure that compiler features detected in the header
|
|
||||||
# match the features detected in CMake.
|
|
||||||
if (SUPPORTS_USER_DEFINED_LITERALS)
|
|
||||||
set(supports_udl 1)
|
|
||||||
else ()
|
|
||||||
set(supports_udl 0)
|
|
||||||
endif ()
|
|
||||||
expect_compile(udl-check "
|
|
||||||
#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
|
|
||||||
# error
|
|
||||||
#endif
|
|
||||||
")
|
|
||||||
|
|
||||||
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||||
# Compile-time argument type check
|
# Compile-time argument type check
|
||||||
expect_compile(format-string-number-spec "
|
expect_compile(format-string-number-spec "
|
||||||
|
|||||||
@ -1,61 +0,0 @@
|
|||||||
// Formatting library for C++ - formatting library tests
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// For the license information refer to format.h.
|
|
||||||
|
|
||||||
#include "fmt/compile.h"
|
|
||||||
#include "gmock/gmock.h"
|
|
||||||
|
|
||||||
#if FMT_USE_CONSTEVAL
|
|
||||||
|
|
||||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
|
||||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
|
||||||
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
|
||||||
}
|
|
||||||
Char buffer[max_string_length]{};
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t max_string_length, typename Char = char, typename... Args>
|
|
||||||
consteval auto test_format(auto format, const Args&... args) {
|
|
||||||
test_string<max_string_length, Char> string{};
|
|
||||||
fmt::format_to(string.buffer, format, args...);
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(compile_time_formatting_test, floating_point) {
|
|
||||||
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
|
|
||||||
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
|
|
||||||
|
|
||||||
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
|
|
||||||
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
|
|
||||||
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
|
|
||||||
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
|
|
||||||
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
|
|
||||||
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
|
|
||||||
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
|
|
||||||
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
|
|
||||||
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
|
|
||||||
|
|
||||||
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
|
|
||||||
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
|
|
||||||
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
|
|
||||||
EXPECT_EQ("9223372036854775808.000000",
|
|
||||||
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
|
|
||||||
|
|
||||||
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
|
|
||||||
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
|
|
||||||
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
|
|
||||||
if (std::signbit(-nan))
|
|
||||||
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
|
|
||||||
else
|
|
||||||
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
|
||||||
|
|
||||||
constexpr double inf = std::numeric_limits<double>::infinity();
|
|
||||||
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
|
|
||||||
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
|
|
||||||
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // FMT_USE_CONSTEVAL
|
|
||||||
@ -7,21 +7,16 @@
|
|||||||
|
|
||||||
#include "fmt/compile.h"
|
#include "fmt/compile.h"
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <list>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "fmt/chrono.h"
|
#include "fmt/chrono.h"
|
||||||
#include "fmt/ranges.h"
|
#include "fmt/ranges.h"
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
|
|
||||||
TEST(iterator_test, counting_iterator) {
|
|
||||||
auto it = fmt::detail::counting_iterator();
|
|
||||||
auto prev = it++;
|
|
||||||
EXPECT_EQ(prev.count(), 0);
|
|
||||||
EXPECT_EQ(it.count(), 1);
|
|
||||||
EXPECT_EQ((it + 41).count(), 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(compile_test, compile_fallback) {
|
TEST(compile_test, compile_fallback) {
|
||||||
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
||||||
// not available.
|
// not available.
|
||||||
@ -95,9 +90,6 @@ TEST(compile_test, format_escape) {
|
|||||||
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
|
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(compile_test, format_wide_string) {
|
|
||||||
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(compile_test, format_specs) {
|
TEST(compile_test, format_specs) {
|
||||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
||||||
@ -129,7 +121,6 @@ TEST(compile_test, manual_ordering) {
|
|||||||
"true 42 42 foo 0x1234 foo",
|
"true 42 42 foo 0x1234 foo",
|
||||||
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
|
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
|
||||||
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
|
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
|
||||||
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(compile_test, named) {
|
TEST(compile_test, named) {
|
||||||
@ -138,10 +129,6 @@ TEST(compile_test, named) {
|
|||||||
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
|
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
|
||||||
fmt::detail::runtime_named_field<char>>);
|
fmt::detail::runtime_named_field<char>>);
|
||||||
|
|
||||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
|
|
||||||
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
|
|
||||||
fmt::arg("arg", 43)));
|
|
||||||
|
|
||||||
EXPECT_EQ("foobar",
|
EXPECT_EQ("foobar",
|
||||||
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
|
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
|
||||||
fmt::arg("a1", "bar")));
|
fmt::arg("a1", "bar")));
|
||||||
@ -206,7 +193,22 @@ TEST(compile_test, format_to_n) {
|
|||||||
EXPECT_STREQ("2a", buffer);
|
EXPECT_STREQ("2a", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef __cpp_lib_bit_cast
|
TEST(compile_test, output_iterators) {
|
||||||
|
std::list<char> out;
|
||||||
|
fmt::format_to(std::back_inserter(out), FMT_COMPILE("{}"), 42);
|
||||||
|
EXPECT_EQ("42", std::string(out.begin(), out.end()));
|
||||||
|
|
||||||
|
std::stringstream s;
|
||||||
|
fmt::format_to(std::ostream_iterator<char>(s), FMT_COMPILE("{}"), 42);
|
||||||
|
EXPECT_EQ("42", s.str());
|
||||||
|
|
||||||
|
std::stringstream s2;
|
||||||
|
fmt::format_to(std::ostreambuf_iterator<char>(s2), FMT_COMPILE("{}.{:06d}"),
|
||||||
|
42, 43);
|
||||||
|
EXPECT_EQ("42.000043", s2.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
# if FMT_USE_CONSTEVAL && (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940)
|
||||||
TEST(compile_test, constexpr_formatted_size) {
|
TEST(compile_test, constexpr_formatted_size) {
|
||||||
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
||||||
EXPECT_EQ(size, 2);
|
EXPECT_EQ(size, 2);
|
||||||
@ -226,6 +228,12 @@ TEST(compile_test, constexpr_formatted_size) {
|
|||||||
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
|
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
|
||||||
EXPECT_EQ(str_size, 3);
|
EXPECT_EQ(str_size, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, static_format) {
|
||||||
|
constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
|
||||||
|
EXPECT_STREQ(result.c_str(), "42");
|
||||||
|
EXPECT_EQ(result.str(), "42");
|
||||||
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
TEST(compile_test, text_and_arg) {
|
TEST(compile_test, text_and_arg) {
|
||||||
@ -237,10 +245,14 @@ TEST(compile_test, unknown_format_fallback) {
|
|||||||
EXPECT_EQ(" 42 ",
|
EXPECT_EQ(" 42 ",
|
||||||
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
|
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
|
||||||
|
|
||||||
std::vector<char> v;
|
std::vector<char> v1;
|
||||||
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"),
|
fmt::format_to(std::back_inserter(v1), FMT_COMPILE("{}"), 42);
|
||||||
|
EXPECT_EQ("42", fmt::string_view(v1.data(), v1.size()));
|
||||||
|
|
||||||
|
std::vector<char> v2;
|
||||||
|
fmt::format_to(std::back_inserter(v2), FMT_COMPILE("{name:^4}"),
|
||||||
fmt::arg("name", 42));
|
fmt::arg("name", 42));
|
||||||
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size()));
|
EXPECT_EQ(" 42 ", fmt::string_view(v2.data(), v2.size()));
|
||||||
|
|
||||||
char buffer[4];
|
char buffer[4];
|
||||||
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
|
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
|
||||||
@ -273,11 +285,23 @@ TEST(compile_test, to_string_and_formatter) {
|
|||||||
fmt::format(FMT_COMPILE("{}"), to_stringable());
|
fmt::format(FMT_COMPILE("{}"), to_stringable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct std_context_test {};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<std_context_test> : formatter<int> {
|
||||||
|
auto format(std_context_test, format_context& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(compile_test, print) {
|
TEST(compile_test, print) {
|
||||||
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
|
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
|
||||||
"Don't panic!");
|
"Don't panic!");
|
||||||
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
|
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
|
||||||
"Don't panic!");
|
"Don't panic!");
|
||||||
|
fmt::print(FMT_COMPILE("{}"), std_context_test());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -286,7 +310,17 @@ TEST(compile_test, compile_format_string_literal) {
|
|||||||
using namespace fmt::literals;
|
using namespace fmt::literals;
|
||||||
EXPECT_EQ("", fmt::format(""_cf));
|
EXPECT_EQ("", fmt::format(""_cf));
|
||||||
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
|
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
|
||||||
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||||
|
template <typename S> auto check_is_compiled_string(const S&) -> bool {
|
||||||
|
return fmt::is_compiled_string<S>::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, is_compiled_string) {
|
||||||
|
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("asdf")));
|
||||||
|
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("{}")));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -301,7 +335,7 @@ TEST(compile_test, compile_format_string_literal) {
|
|||||||
(FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930)) && \
|
(FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930)) && \
|
||||||
defined(__cpp_lib_is_constant_evaluated)
|
defined(__cpp_lib_is_constant_evaluated)
|
||||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
template <typename T> constexpr auto operator==(const T& rhs) const -> bool {
|
||||||
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
||||||
}
|
}
|
||||||
Char buffer[max_string_length]{};
|
Char buffer[max_string_length]{};
|
||||||
@ -342,6 +376,7 @@ TEST(compile_time_formatting_test, integer) {
|
|||||||
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
|
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
|
||||||
|
|
||||||
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
|
||||||
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42l));
|
||||||
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
|
||||||
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
|
||||||
|
|
||||||
@ -382,4 +417,53 @@ TEST(compile_time_formatting_test, custom_type) {
|
|||||||
TEST(compile_time_formatting_test, multibyte_fill) {
|
TEST(compile_time_formatting_test, multibyte_fill) {
|
||||||
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
|
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, floating_point) {
|
||||||
|
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
|
||||||
|
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
|
||||||
|
|
||||||
|
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
|
||||||
|
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
|
||||||
|
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
|
||||||
|
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
|
||||||
|
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
|
||||||
|
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
|
||||||
|
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
|
||||||
|
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
|
||||||
|
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
|
||||||
|
|
||||||
|
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
|
||||||
|
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
|
||||||
|
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
|
||||||
|
EXPECT_EQ("9223372036854775808.000000",
|
||||||
|
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
|
||||||
|
|
||||||
|
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
|
||||||
|
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
|
||||||
|
if (std::signbit(-nan))
|
||||||
|
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
|
||||||
|
else
|
||||||
|
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
||||||
|
|
||||||
|
constexpr double inf = std::numeric_limits<double>::infinity();
|
||||||
|
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
|
||||||
|
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
|
||||||
|
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if FMT_USE_CONSTEXPR_STRING
|
||||||
|
TEST(compile_test, constexpr_string_format) {
|
||||||
|
constexpr auto result = []() {
|
||||||
|
return fmt::format(FMT_COMPILE("{}"), 42) == "42";
|
||||||
|
}();
|
||||||
|
EXPECT_TRUE(result);
|
||||||
|
|
||||||
|
// Test with a larger string to avoid small string optimization.
|
||||||
|
constexpr auto big = []() {
|
||||||
|
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
|
||||||
|
}();
|
||||||
|
EXPECT_TRUE(big);
|
||||||
|
}
|
||||||
|
#endif // FMT_USE_CONSTEXPR_STRING
|
||||||
|
|||||||
@ -283,7 +283,7 @@ struct double_double {
|
|||||||
double a;
|
double a;
|
||||||
double b;
|
double b;
|
||||||
|
|
||||||
explicit constexpr double_double(double a_val = 0, double b_val = 0)
|
constexpr explicit double_double(double a_val = 0, double b_val = 0)
|
||||||
: a(a_val), b(b_val) {}
|
: a(a_val), b(b_val) {}
|
||||||
|
|
||||||
operator double() const { return a + b; }
|
operator double() const { return a + b; }
|
||||||
@ -292,14 +292,14 @@ struct double_double {
|
|||||||
|
|
||||||
auto format_as(double_double d) -> double { return d; }
|
auto format_as(double_double d) -> double { return d; }
|
||||||
|
|
||||||
bool operator>=(const double_double& lhs, const double_double& rhs) {
|
auto operator>=(const double_double& lhs, const double_double& rhs) -> bool {
|
||||||
return lhs.a + lhs.b >= rhs.a + rhs.b;
|
return lhs.a + lhs.b >= rhs.a + rhs.b;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct slow_float {
|
struct slow_float {
|
||||||
float value;
|
float value;
|
||||||
|
|
||||||
explicit constexpr slow_float(float val = 0) : value(val) {}
|
constexpr explicit slow_float(float val = 0) : value(val) {}
|
||||||
operator float() const { return value; }
|
operator float() const { return value; }
|
||||||
auto operator-() const -> slow_float { return slow_float(-value); }
|
auto operator-() const -> slow_float { return slow_float(-value); }
|
||||||
};
|
};
|
||||||
@ -307,19 +307,20 @@ struct slow_float {
|
|||||||
auto format_as(slow_float f) -> float { return f; }
|
auto format_as(slow_float f) -> float { return f; }
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
template <> struct is_floating_point<double_double> : std::true_type {};
|
|
||||||
template <> struct numeric_limits<double_double> {
|
template <> struct numeric_limits<double_double> {
|
||||||
// is_iec559 is true for double-double in libstdc++.
|
// is_iec559 is true for double-double in libstdc++.
|
||||||
static constexpr bool is_iec559 = true;
|
static constexpr bool is_iec559 = true;
|
||||||
static constexpr int digits = 106;
|
static constexpr int digits = 106;
|
||||||
|
static constexpr int digits10 = 33;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct is_floating_point<slow_float> : std::true_type {};
|
|
||||||
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
|
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
template <> struct is_floating_point<double_double> : std::true_type {};
|
||||||
|
template <> struct is_floating_point<slow_float> : std::true_type {};
|
||||||
template <> struct is_fast_float<slow_float> : std::false_type {};
|
template <> struct is_fast_float<slow_float> : std::false_type {};
|
||||||
namespace dragonbox {
|
namespace dragonbox {
|
||||||
template <> struct float_info<slow_float> {
|
template <> struct float_info<slow_float> {
|
||||||
@ -341,7 +342,7 @@ TEST(format_impl_test, write_dragon_even) {
|
|||||||
auto s = std::string();
|
auto s = std::string();
|
||||||
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
|
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
|
||||||
// Specializing is_floating_point is broken in MSVC.
|
// Specializing is_floating_point is broken in MSVC.
|
||||||
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
|
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "3.355445e+07");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
|
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
|
||||||
@ -355,11 +356,11 @@ TEST(format_impl_test, write_console_signature) {
|
|||||||
|
|
||||||
// A public domain branchless UTF-8 decoder by Christopher Wellons:
|
// A public domain branchless UTF-8 decoder by Christopher Wellons:
|
||||||
// https://github.com/skeeto/branchless-utf8
|
// https://github.com/skeeto/branchless-utf8
|
||||||
constexpr bool unicode_is_surrogate(uint32_t c) {
|
constexpr auto unicode_is_surrogate(uint32_t c) -> bool {
|
||||||
return c >= 0xD800U && c <= 0xDFFFU;
|
return c >= 0xD800U && c <= 0xDFFFU;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
|
FMT_CONSTEXPR auto utf8_encode(char* s, uint32_t c) -> char* {
|
||||||
if (c >= (1UL << 16)) {
|
if (c >= (1UL << 16)) {
|
||||||
s[0] = static_cast<char>(0xf0 | (c >> 18));
|
s[0] = static_cast<char>(0xf0 | (c >> 18));
|
||||||
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));
|
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));
|
||||||
|
|||||||
@ -26,11 +26,17 @@
|
|||||||
#include <string> // std::string
|
#include <string> // std::string
|
||||||
#include <thread> // std::thread
|
#include <thread> // std::thread
|
||||||
#include <type_traits> // std::is_default_constructible
|
#include <type_traits> // std::is_default_constructible
|
||||||
|
#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<version>)
|
||||||
|
# include <version>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "mock-allocator.h"
|
#include "mock-allocator.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
using fmt::basic_memory_buffer;
|
using fmt::basic_memory_buffer;
|
||||||
using fmt::format_error;
|
using fmt::format_error;
|
||||||
using fmt::memory_buffer;
|
using fmt::memory_buffer;
|
||||||
@ -42,6 +48,10 @@ using fmt::detail::uint128_fallback;
|
|||||||
using testing::Return;
|
using testing::Return;
|
||||||
using testing::StrictMock;
|
using testing::StrictMock;
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_concepts
|
||||||
|
static_assert(std::output_iterator<fmt::appender, char>);
|
||||||
|
#endif
|
||||||
|
|
||||||
enum { buffer_size = 256 };
|
enum { buffer_size = 256 };
|
||||||
|
|
||||||
TEST(uint128_test, ctor) {
|
TEST(uint128_test, ctor) {
|
||||||
@ -196,10 +206,6 @@ TEST(util_test, parse_nonnegative_int) {
|
|||||||
EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
|
EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(format_impl_test, compute_width) {
|
|
||||||
EXPECT_EQ(fmt::detail::compute_width("вожык"), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(util_test, utf8_to_utf16) {
|
TEST(util_test, utf8_to_utf16) {
|
||||||
auto u = fmt::detail::utf8_to_utf16("лошадка");
|
auto u = fmt::detail::utf8_to_utf16("лошадка");
|
||||||
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
|
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
|
||||||
@ -309,18 +315,17 @@ TEST(memory_buffer_test, move_ctor_inline_buffer) {
|
|||||||
std::allocator<char>* alloc = buffer.get_allocator().get();
|
std::allocator<char>* alloc = buffer.get_allocator().get();
|
||||||
basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
|
basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
|
||||||
// Move shouldn't destroy the inline content of the first buffer.
|
// Move shouldn't destroy the inline content of the first buffer.
|
||||||
EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
|
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
|
||||||
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
|
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
|
||||||
EXPECT_EQ(5u, buffer2.capacity());
|
EXPECT_EQ(buffer2.capacity(), 5u);
|
||||||
// Move should transfer allocator.
|
// Move should transfer allocator.
|
||||||
EXPECT_EQ(nullptr, buffer.get_allocator().get());
|
EXPECT_EQ(buffer.get_allocator().get(), nullptr);
|
||||||
EXPECT_EQ(alloc, buffer2.get_allocator().get());
|
EXPECT_EQ(buffer2.get_allocator().get(), alloc);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto alloc = std::allocator<char>();
|
auto alloc = std::allocator<char>();
|
||||||
basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
|
basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
|
||||||
const char test[] = "test";
|
buffer.append(string_view("test"));
|
||||||
buffer.append(string_view(test, 4));
|
|
||||||
check_move_buffer("test", buffer);
|
check_move_buffer("test", buffer);
|
||||||
// Adding one more character fills the inline buffer, but doesn't cause
|
// Adding one more character fills the inline buffer, but doesn't cause
|
||||||
// dynamic allocation.
|
// dynamic allocation.
|
||||||
@ -345,14 +350,63 @@ TEST(memory_buffer_test, move_ctor_dynamic_buffer) {
|
|||||||
EXPECT_GT(buffer2.capacity(), 4u);
|
EXPECT_GT(buffer2.capacity(), 4u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using std_allocator_noprop = allocator_ref<std::allocator<char>, false>;
|
||||||
|
|
||||||
|
TEST(memory_buffer_test, move_ctor_inline_buffer_non_propagating) {
|
||||||
|
auto check_move_buffer =
|
||||||
|
[](const char* str,
|
||||||
|
basic_memory_buffer<char, 5, std_allocator_noprop>& buffer) {
|
||||||
|
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
|
||||||
|
const char* original_data_ptr = &buffer[0];
|
||||||
|
basic_memory_buffer<char, 5, std_allocator_noprop> buffer2(
|
||||||
|
std::move(buffer));
|
||||||
|
const char* new_data_ptr = &buffer2[0];
|
||||||
|
EXPECT_NE(new_data_ptr, original_data_ptr);
|
||||||
|
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
|
||||||
|
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), str);
|
||||||
|
EXPECT_EQ(buffer2.capacity(), 5u);
|
||||||
|
// Allocators should NOT be transferred; they remain distinct instances.
|
||||||
|
// The original buffer's allocator pointer should still be valid (not
|
||||||
|
// nullptr).
|
||||||
|
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
|
||||||
|
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
|
||||||
|
};
|
||||||
|
auto alloc = std::allocator<char>();
|
||||||
|
basic_memory_buffer<char, 5, std_allocator_noprop> buffer(
|
||||||
|
(std_allocator_noprop(&alloc)));
|
||||||
|
buffer.append(string_view("test", 4));
|
||||||
|
check_move_buffer("test", buffer);
|
||||||
|
buffer.push_back('a');
|
||||||
|
check_move_buffer("testa", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(memory_buffer_test, move_ctor_dynamic_buffer_non_propagating) {
|
||||||
|
auto alloc = std::allocator<char>();
|
||||||
|
basic_memory_buffer<char, 4, std_allocator_noprop> buffer(
|
||||||
|
(std_allocator_noprop(&alloc)));
|
||||||
|
const char test[] = "test";
|
||||||
|
buffer.append(test, test + 4);
|
||||||
|
const char* inline_buffer_ptr = &buffer[0];
|
||||||
|
buffer.push_back('a');
|
||||||
|
EXPECT_NE(buffer.data(), inline_buffer_ptr);
|
||||||
|
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
|
||||||
|
basic_memory_buffer<char, 4, std_allocator_noprop> buffer2;
|
||||||
|
buffer2 = std::move(buffer);
|
||||||
|
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testa");
|
||||||
|
EXPECT_GT(buffer2.capacity(), 4u);
|
||||||
|
EXPECT_NE(buffer2.data(), inline_buffer_ptr);
|
||||||
|
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
|
||||||
|
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
void check_move_assign_buffer(const char* str,
|
void check_move_assign_buffer(const char* str,
|
||||||
basic_memory_buffer<char, 5>& buffer) {
|
basic_memory_buffer<char, 5>& buffer) {
|
||||||
basic_memory_buffer<char, 5> buffer2;
|
basic_memory_buffer<char, 5> buffer2;
|
||||||
buffer2 = std::move(buffer);
|
buffer2 = std::move(buffer);
|
||||||
// Move shouldn't destroy the inline content of the first buffer.
|
// Move shouldn't destroy the inline content of the first buffer.
|
||||||
EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
|
EXPECT_EQ(std::string(&buffer[0], buffer.size()), str);
|
||||||
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
|
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
|
||||||
EXPECT_EQ(5u, buffer2.capacity());
|
EXPECT_EQ(buffer2.capacity(), 5u);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(memory_buffer_test, move_assignment) {
|
TEST(memory_buffer_test, move_assignment) {
|
||||||
@ -371,8 +425,8 @@ TEST(memory_buffer_test, move_assignment) {
|
|||||||
basic_memory_buffer<char, 5> buffer2;
|
basic_memory_buffer<char, 5> buffer2;
|
||||||
buffer2 = std::move(buffer);
|
buffer2 = std::move(buffer);
|
||||||
// Move should rip the guts of the first buffer.
|
// Move should rip the guts of the first buffer.
|
||||||
EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
|
EXPECT_EQ(buffer.data(), inline_buffer_ptr);
|
||||||
EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
|
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testab");
|
||||||
EXPECT_GT(buffer2.capacity(), 5u);
|
EXPECT_GT(buffer2.capacity(), 5u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,6 +525,18 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
|
|||||||
EXPECT_THROW(buffer.resize(161), std::exception);
|
EXPECT_THROW(buffer.resize(161), std::exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(memory_buffer_test, back_insert_iterator) {
|
||||||
|
fmt::memory_buffer buf;
|
||||||
|
using iterator = decltype(std::back_inserter(buf));
|
||||||
|
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<iterator>::value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(format_test, digits2_alignment) {
|
||||||
|
auto p =
|
||||||
|
fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0));
|
||||||
|
EXPECT_EQ(p % 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(format_test, exception_from_lib) {
|
TEST(format_test, exception_from_lib) {
|
||||||
EXPECT_THROW_MSG(fmt::report_error("test"), format_error, "test");
|
EXPECT_THROW_MSG(fmt::report_error("test"), format_error, "test");
|
||||||
}
|
}
|
||||||
@ -536,6 +602,10 @@ TEST(format_test, arg_errors) {
|
|||||||
format_error, "argument not found");
|
format_error, "argument not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(format_test, display_width_precision) {
|
||||||
|
EXPECT_EQ(fmt::format("{:.5}", "🐱🐱🐱"), "🐱🐱");
|
||||||
|
}
|
||||||
|
|
||||||
template <int N> struct test_format {
|
template <int N> struct test_format {
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
static auto format(fmt::string_view fmt, const T&... args) -> std::string {
|
static auto format(fmt::string_view fmt, const T&... args) -> std::string {
|
||||||
@ -566,6 +636,9 @@ TEST(format_test, named_arg) {
|
|||||||
EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
|
EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
|
||||||
fmt::arg("A_", "A"), fmt::arg("_1", 1)));
|
fmt::arg("A_", "A"), fmt::arg("_1", 1)));
|
||||||
EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42");
|
EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42");
|
||||||
|
EXPECT_EQ(fmt::format("{value:{width}}", fmt::arg("value", -42),
|
||||||
|
fmt::arg("width", 4)),
|
||||||
|
" -42");
|
||||||
EXPECT_EQ("st",
|
EXPECT_EQ("st",
|
||||||
fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
|
fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
|
||||||
EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");
|
EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");
|
||||||
@ -583,6 +656,9 @@ TEST(format_test, named_arg) {
|
|||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{a} {}"), fmt::arg("a", 2), 42),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{a} {}"), fmt::arg("a", 2), 42),
|
||||||
format_error,
|
format_error,
|
||||||
"cannot switch from manual to automatic argument indexing");
|
"cannot switch from manual to automatic argument indexing");
|
||||||
|
EXPECT_THROW_MSG(
|
||||||
|
(void)fmt::format("{a}", fmt::arg("a", 1), fmt::arg("a", 10)),
|
||||||
|
format_error, "duplicate named arg");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(format_test, auto_arg_index) {
|
TEST(format_test, auto_arg_index) {
|
||||||
@ -794,7 +870,7 @@ TEST(format_test, hash_flag) {
|
|||||||
EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50");
|
EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50");
|
||||||
EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0.");
|
EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0.");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error,
|
||||||
"missing '}' in format string");
|
"invalid format specifier for char");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
|
||||||
"invalid format specifier for char");
|
"invalid format specifier for char");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
|
||||||
@ -815,7 +891,7 @@ TEST(format_test, zero_flag) {
|
|||||||
EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042");
|
EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042");
|
||||||
EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042");
|
EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error,
|
||||||
"missing '}' in format string");
|
"invalid format specifier for char");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error,
|
||||||
"invalid format specifier for char");
|
"invalid format specifier for char");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error,
|
||||||
@ -855,6 +931,7 @@ TEST(format_test, width) {
|
|||||||
" 0xcafe");
|
" 0xcafe");
|
||||||
EXPECT_EQ(fmt::format("{:11}", 'x'), "x ");
|
EXPECT_EQ(fmt::format("{:11}", 'x'), "x ");
|
||||||
EXPECT_EQ(fmt::format("{:12}", "str"), "str ");
|
EXPECT_EQ(fmt::format("{:12}", "str"), "str ");
|
||||||
|
EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "*🤡**");
|
||||||
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
|
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
|
||||||
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
|
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
|
||||||
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
|
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
|
||||||
@ -862,6 +939,35 @@ TEST(format_test, width) {
|
|||||||
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
|
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(format_test, debug_presentation) {
|
||||||
|
EXPECT_EQ(fmt::format("{:?}", ""), R"("")");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.0?}", "\n"), R"(*****)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.1?}", "\n"), R"("****)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.2?}", "\n"), R"("\***)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.3?}", "\n"), R"("\n**)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.4?}", "\n"), R"("\n"*)");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.1?}", "Σ"), R"("****)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.2?}", "Σ"), R"("Σ***)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.3?}", "Σ"), R"("Σ"**)");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.1?}", "笑"), R"("****)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.2?}", "笑"), R"("****)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.3?}", "笑"), R"("笑**)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*<5.4?}", "笑"), R"("笑"*)");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:*<8?}", "туда"), R"("туда"**)");
|
||||||
|
EXPECT_EQ(fmt::format("{:*>8?}", "сюда"), R"(**"сюда")");
|
||||||
|
EXPECT_EQ(fmt::format("{:*^8?}", "中心"), R"(*"中心"*)");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:*^14?}", "A\t👈🤯ы猫"), R"(*"A\t👈🤯ы猫"*)");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES
|
||||||
|
? "width/precision is out of range"
|
||||||
|
: "width/precision is not integer";
|
||||||
|
|
||||||
TEST(format_test, runtime_width) {
|
TEST(format_test, runtime_width) {
|
||||||
auto int_maxer = std::to_string(INT_MAX + 1u);
|
auto int_maxer = std::to_string(INT_MAX + 1u);
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0),
|
||||||
@ -884,23 +990,23 @@ TEST(format_test, runtime_width) {
|
|||||||
"invalid format string");
|
"invalid format string");
|
||||||
|
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error,
|
||||||
"negative width");
|
"width/precision is out of range");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)),
|
||||||
format_error, "number is too big");
|
format_error, bad_dynamic_spec_msg);
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error,
|
||||||
"negative width");
|
bad_dynamic_spec_msg);
|
||||||
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
|
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
|
||||||
long value = INT_MAX;
|
long value = INT_MAX;
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)),
|
||||||
format_error, "number is too big");
|
format_error, bad_dynamic_spec_msg);
|
||||||
}
|
}
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)),
|
||||||
format_error, "number is too big");
|
format_error, bad_dynamic_spec_msg);
|
||||||
|
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error,
|
||||||
"width is not integer");
|
"width/precision is not integer");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
|
||||||
"width is not integer");
|
"width/precision is not integer");
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42");
|
EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42");
|
||||||
EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), " 42");
|
EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), " 42");
|
||||||
@ -917,6 +1023,10 @@ TEST(format_test, runtime_width) {
|
|||||||
EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42");
|
EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(format_test, exponent_range) {
|
||||||
|
for (int e = -1074; e <= 1023; ++e) (void)fmt::format("{}", std::ldexp(1, e));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(format_test, precision) {
|
TEST(format_test, precision) {
|
||||||
char format_str[buffer_size];
|
char format_str[buffer_size];
|
||||||
safe_sprintf(format_str, "{0:.%u", UINT_MAX);
|
safe_sprintf(format_str, "{0:.%u", UINT_MAX);
|
||||||
@ -939,7 +1049,7 @@ TEST(format_test, precision) {
|
|||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error,
|
||||||
"invalid precision");
|
"invalid precision");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error,
|
||||||
"invalid precision");
|
"invalid format string");
|
||||||
|
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
|
||||||
"invalid format specifier");
|
"invalid format specifier");
|
||||||
@ -1044,7 +1154,8 @@ TEST(format_test, precision) {
|
|||||||
EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123.");
|
EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123.");
|
||||||
EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23");
|
EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23");
|
||||||
EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001");
|
EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001");
|
||||||
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400");
|
EXPECT_EQ(fmt::format("{}", 123456789.0f), "1.2345679e+08");
|
||||||
|
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1.0196664e+09");
|
||||||
EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
|
EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
|
||||||
EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
|
EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
|
||||||
|
|
||||||
@ -1054,21 +1165,45 @@ TEST(format_test, precision) {
|
|||||||
EXPECT_THROW_MSG(
|
EXPECT_THROW_MSG(
|
||||||
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
|
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
|
||||||
format_error, "invalid format specifier");
|
format_error, "invalid format specifier");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
|
|
||||||
fmt::detail::max_value<int>()),
|
|
||||||
format_error, "number is too big");
|
|
||||||
EXPECT_THROW_MSG(
|
EXPECT_THROW_MSG(
|
||||||
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
|
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
|
||||||
format_error, "number is too big");
|
format_error, "number is too big");
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.f}"), 42.0), format_error,
|
||||||
|
"invalid format string");
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{0:.2}", "str"), "st");
|
EXPECT_EQ(fmt::format("{0:.2}", "str"), "st");
|
||||||
EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык");
|
EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык");
|
||||||
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
|
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, utf8_precision) {
|
TEST(format_test, large_precision) {
|
||||||
|
// Iterator used to abort the actual output.
|
||||||
|
struct throwing_iterator {
|
||||||
|
auto operator=(char) -> throwing_iterator& {
|
||||||
|
throw std::runtime_error("aborted");
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
auto operator*() -> throwing_iterator& { return *this; }
|
||||||
|
auto operator++() -> throwing_iterator& { return *this; }
|
||||||
|
auto operator++(int) -> throwing_iterator { return *this; }
|
||||||
|
};
|
||||||
|
auto it = throwing_iterator();
|
||||||
|
|
||||||
|
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}}"), 1.0,
|
||||||
|
fmt::detail::max_value<int>()),
|
||||||
|
std::runtime_error, "aborted");
|
||||||
|
|
||||||
|
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}e}"), 1.0,
|
||||||
|
fmt::detail::max_value<int>() - 1),
|
||||||
|
std::runtime_error, "aborted");
|
||||||
|
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:.{}e}"), 42.0,
|
||||||
|
fmt::detail::max_value<int>()),
|
||||||
|
format_error, "number is too big");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(format_test, utf8_precision) {
|
||||||
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
|
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
|
||||||
EXPECT_EQ(fmt::detail::compute_width(result), 4);
|
|
||||||
EXPECT_EQ(result, "caf\u00e9");
|
EXPECT_EQ(result, "caf\u00e9");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1103,23 +1238,23 @@ TEST(format_test, runtime_precision) {
|
|||||||
"invalid format string");
|
"invalid format string");
|
||||||
|
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1),
|
||||||
format_error, "negative precision");
|
format_error, "width/precision is out of range");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)),
|
||||||
format_error, "number is too big");
|
format_error, bad_dynamic_spec_msg);
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l),
|
||||||
format_error, "negative precision");
|
format_error, bad_dynamic_spec_msg);
|
||||||
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
|
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
|
||||||
long value = INT_MAX;
|
long value = INT_MAX;
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)),
|
||||||
format_error, "number is too big");
|
format_error, bad_dynamic_spec_msg);
|
||||||
}
|
}
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)),
|
||||||
format_error, "number is too big");
|
format_error, bad_dynamic_spec_msg);
|
||||||
|
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'),
|
||||||
format_error, "precision is not integer");
|
format_error, "width/precision is not integer");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0),
|
||||||
format_error, "precision is not integer");
|
format_error, "width/precision is not integer");
|
||||||
|
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
|
||||||
"invalid format specifier");
|
"invalid format specifier");
|
||||||
@ -1650,6 +1785,20 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
|
|||||||
EXPECT_EQ("'foo'",
|
EXPECT_EQ("'foo'",
|
||||||
fmt::format("{}", explicitly_convertible_to_std_string_view()));
|
fmt::format("{}", explicitly_convertible_to_std_string_view()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct convertible_to_std_string_view {
|
||||||
|
operator std::string_view() const noexcept { return "Hi there"; }
|
||||||
|
};
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <>
|
||||||
|
class formatter<convertible_to_std_string_view>
|
||||||
|
: public formatter<std::string_view> {};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(format_test, format_implicitly_convertible_and_inherits_string_view) {
|
||||||
|
static_assert(fmt::is_formattable<convertible_to_std_string_view>{}, "");
|
||||||
|
EXPECT_EQ("Hi there", fmt::format("{}", convertible_to_std_string_view{}));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Answer {};
|
class Answer {};
|
||||||
@ -1708,19 +1857,15 @@ TEST(format_test, format_examples) {
|
|||||||
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
|
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
|
||||||
EXPECT_EQ("The answer is 42.", to_string(out));
|
EXPECT_EQ("The answer is 42.", to_string(out));
|
||||||
|
|
||||||
const char* filename = "nonexistent";
|
EXPECT_THROW(
|
||||||
FILE* ftest = safe_fopen(filename, "r");
|
|
||||||
if (ftest) fclose(ftest);
|
|
||||||
int error_code = errno;
|
|
||||||
EXPECT_TRUE(ftest == nullptr);
|
|
||||||
EXPECT_SYSTEM_ERROR(
|
|
||||||
{
|
{
|
||||||
FILE* f = safe_fopen(filename, "r");
|
const char* filename = "madeup";
|
||||||
if (!f)
|
FILE* file = fopen(filename, "r");
|
||||||
throw fmt::system_error(errno, "Cannot open file '{}'", filename);
|
if (!file)
|
||||||
fclose(f);
|
throw fmt::system_error(errno, "cannot open file '{}'", filename);
|
||||||
|
fclose(file);
|
||||||
},
|
},
|
||||||
error_code, "Cannot open file 'nonexistent'");
|
std::system_error);
|
||||||
|
|
||||||
EXPECT_EQ("First, thou shalt count to three",
|
EXPECT_EQ("First, thou shalt count to three",
|
||||||
fmt::format("First, thou shalt count to {0}", "three"));
|
fmt::format("First, thou shalt count to {0}", "three"));
|
||||||
@ -1780,33 +1925,6 @@ TEST(format_test, big_print) {
|
|||||||
EXPECT_WRITE(stdout, big_print(), std::string(count, 'x'));
|
EXPECT_WRITE(stdout, big_print(), std::string(count, 'x'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows CRT implements _IOLBF incorrectly (full buffering).
|
|
||||||
#if FMT_USE_FCNTL && !defined(_WIN32)
|
|
||||||
TEST(format_test, line_buffering) {
|
|
||||||
auto pipe = fmt::pipe();
|
|
||||||
|
|
||||||
int write_fd = pipe.write_end.descriptor();
|
|
||||||
auto write_end = pipe.write_end.fdopen("w");
|
|
||||||
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
|
|
||||||
write_end.print("42\n");
|
|
||||||
close(write_fd);
|
|
||||||
try {
|
|
||||||
write_end.close();
|
|
||||||
} catch (const std::system_error&) {
|
|
||||||
}
|
|
||||||
|
|
||||||
auto read_end = pipe.read_end.fdopen("r");
|
|
||||||
std::thread reader([&]() {
|
|
||||||
int n = 0;
|
|
||||||
int result = fscanf(read_end.get(), "%d", &n);
|
|
||||||
(void)result;
|
|
||||||
EXPECT_EQ(n, 42);
|
|
||||||
});
|
|
||||||
|
|
||||||
reader.join();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct deadlockable {
|
struct deadlockable {
|
||||||
int value = 0;
|
int value = 0;
|
||||||
mutable std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
@ -1918,11 +2036,6 @@ TEST(format_test, unpacked_args) {
|
|||||||
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
|
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr char with_null[3] = {'{', '}', '\0'};
|
|
||||||
constexpr char no_null[2] = {'{', '}'};
|
|
||||||
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
|
|
||||||
static constexpr const char static_no_null[2] = {'{', '}'};
|
|
||||||
|
|
||||||
TEST(format_test, compile_time_string) {
|
TEST(format_test, compile_time_string) {
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
|
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
|
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
|
||||||
@ -1937,19 +2050,12 @@ TEST(format_test, compile_time_string) {
|
|||||||
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
|
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
(void)static_with_null;
|
static constexpr char format_str[3] = {'{', '}', '\0'};
|
||||||
(void)static_no_null;
|
(void)format_str;
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
|
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42");
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
(void)with_null;
|
|
||||||
(void)no_null;
|
|
||||||
#if FMT_CPLUSPLUS >= 201703L
|
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
|
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
|
|
||||||
#endif
|
|
||||||
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
||||||
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
|
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
|
||||||
#endif
|
#endif
|
||||||
@ -1965,7 +2071,6 @@ TEST(format_test, custom_format_compile_time_string) {
|
|||||||
EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42");
|
EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_USER_DEFINED_LITERALS
|
|
||||||
TEST(format_test, named_arg_udl) {
|
TEST(format_test, named_arg_udl) {
|
||||||
using namespace fmt::literals;
|
using namespace fmt::literals;
|
||||||
auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra",
|
auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra",
|
||||||
@ -1977,13 +2082,12 @@ TEST(format_test, named_arg_udl) {
|
|||||||
|
|
||||||
EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42");
|
EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42");
|
||||||
}
|
}
|
||||||
#endif // FMT_USE_USER_DEFINED_LITERALS
|
|
||||||
|
|
||||||
TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); }
|
TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); }
|
||||||
|
|
||||||
TEST(format_test, formatter_not_specialized) {
|
TEST(format_test, formatter_not_specialized) {
|
||||||
static_assert(!fmt::has_formatter<fmt::formatter<test_enum>,
|
static_assert(!fmt::is_formattable<fmt::formatter<test_enum>,
|
||||||
fmt::format_context>::value,
|
fmt::format_context>::value,
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1992,6 +2096,8 @@ enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
|
|||||||
auto format_as(big_enum e) -> unsigned long long { return e; }
|
auto format_as(big_enum e) -> unsigned long long { return e; }
|
||||||
|
|
||||||
TEST(format_test, strong_enum) {
|
TEST(format_test, strong_enum) {
|
||||||
|
auto arg = fmt::basic_format_arg<fmt::context>(big_enum_value);
|
||||||
|
EXPECT_EQ(arg.type(), fmt::detail::type::ulong_long_type);
|
||||||
EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000");
|
EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2041,6 +2147,17 @@ TEST(format_test, output_iterators) {
|
|||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
fmt::format_to(std::ostream_iterator<char>(s), "{}", 42);
|
fmt::format_to(std::ostream_iterator<char>(s), "{}", 42);
|
||||||
EXPECT_EQ("42", s.str());
|
EXPECT_EQ("42", s.str());
|
||||||
|
|
||||||
|
std::stringstream s2;
|
||||||
|
fmt::format_to(std::ostreambuf_iterator<char>(s2), "{}.{:06d}", 42, 43);
|
||||||
|
EXPECT_EQ("42.000043", s2.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(format_test, fill_via_appender) {
|
||||||
|
fmt::memory_buffer buf;
|
||||||
|
auto it = fmt::appender(buf);
|
||||||
|
std::fill_n(it, 3, '~');
|
||||||
|
EXPECT_EQ(fmt::to_string(buf), "~~~");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(format_test, formatted_size) {
|
TEST(format_test, formatted_size) {
|
||||||
@ -2156,7 +2273,7 @@ TEST(format_test, vformat_to) {
|
|||||||
fmt::vformat_to(std::back_inserter(s), "{}", args);
|
fmt::vformat_to(std::back_inserter(s), "{}", args);
|
||||||
EXPECT_EQ(s, "42");
|
EXPECT_EQ(s, "42");
|
||||||
s.clear();
|
s.clear();
|
||||||
fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args);
|
fmt::vformat_to(std::back_inserter(s), "{}", args);
|
||||||
EXPECT_EQ(s, "42");
|
EXPECT_EQ(s, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2365,6 +2482,7 @@ namespace adl_test {
|
|||||||
template <typename... T> void make_format_args(const T&...) = delete;
|
template <typename... T> void make_format_args(const T&...) = delete;
|
||||||
|
|
||||||
struct string : std::string {};
|
struct string : std::string {};
|
||||||
|
auto format_as(const string& s) -> std::string { return s; }
|
||||||
} // namespace adl_test
|
} // namespace adl_test
|
||||||
|
|
||||||
// Test that formatting functions compile when make_format_args is found by ADL.
|
// Test that formatting functions compile when make_format_args is found by ADL.
|
||||||
@ -2420,3 +2538,130 @@ TEST(format_test, formatter_overrides_implicit_conversion) {
|
|||||||
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "x");
|
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "x");
|
||||||
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "y");
|
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "y");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ustring {
|
||||||
|
using value_type = unsigned;
|
||||||
|
|
||||||
|
auto find_first_of(value_type, size_t) const -> size_t;
|
||||||
|
|
||||||
|
auto data() const -> const char*;
|
||||||
|
auto size() const -> size_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<ustring> : formatter<std::string> {
|
||||||
|
auto format(const ustring&, format_context& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return formatter<std::string>::format("ustring", ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(format_test, ustring) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", ustring()), "ustring");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(format_test, writer) {
|
||||||
|
auto write_to_stdout = []() {
|
||||||
|
auto w = fmt::writer(stdout);
|
||||||
|
w.print("{}", 42);
|
||||||
|
};
|
||||||
|
EXPECT_WRITE(stdout, write_to_stdout(), "42");
|
||||||
|
|
||||||
|
#if FMT_USE_FCNTL
|
||||||
|
auto pipe = fmt::pipe();
|
||||||
|
auto write_end = pipe.write_end.fdopen("w");
|
||||||
|
fmt::writer(write_end.get()).print("42");
|
||||||
|
write_end.close();
|
||||||
|
auto read_end = pipe.read_end.fdopen("r");
|
||||||
|
int n = 0;
|
||||||
|
int result = fscanf(read_end.get(), "%d", &n);
|
||||||
|
(void)result;
|
||||||
|
EXPECT_EQ(n, 42);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto s = fmt::string_buffer();
|
||||||
|
fmt::writer(s).print("foo");
|
||||||
|
EXPECT_EQ(s.str(), "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_USE_FCNTL && !defined(_WIN32)
|
||||||
|
TEST(format_test, invalid_glibc_buffer) {
|
||||||
|
auto pipe = fmt::pipe();
|
||||||
|
auto write_end = pipe.write_end.fdopen("w");
|
||||||
|
auto file = write_end.get();
|
||||||
|
|
||||||
|
// This results in _IO_write_ptr < _IO_write_end.
|
||||||
|
fprintf(file, "111\n");
|
||||||
|
setvbuf(file, nullptr, _IOLBF, 0);
|
||||||
|
|
||||||
|
fmt::print(file, "------\n");
|
||||||
|
}
|
||||||
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
|
#if FMT_USE_BITINT
|
||||||
|
FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension")
|
||||||
|
|
||||||
|
TEST(format_test, bitint) {
|
||||||
|
using fmt::detail::bitint;
|
||||||
|
using fmt::detail::ubitint;
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", ubitint<3>(7)), "7");
|
||||||
|
EXPECT_EQ(fmt::format("{}", bitint<7>()), "0");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", ubitint<15>(31000)), "31000");
|
||||||
|
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MIN)), "-32768");
|
||||||
|
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MAX)), "32767");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", ubitint<32>(4294967295)), "4294967295");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", ubitint<47>(140737488355327ULL)),
|
||||||
|
"140737488355327");
|
||||||
|
EXPECT_EQ(fmt::format("{}", bitint<47>(-40737488355327LL)),
|
||||||
|
"-40737488355327");
|
||||||
|
|
||||||
|
// Check lvalues and const
|
||||||
|
auto a = bitint<8>(0);
|
||||||
|
auto b = ubitint<32>(4294967295);
|
||||||
|
const auto c = bitint<7>(0);
|
||||||
|
const auto d = ubitint<32>(4294967295);
|
||||||
|
EXPECT_EQ(fmt::format("{}", a), "0");
|
||||||
|
EXPECT_EQ(fmt::format("{}", b), "4294967295");
|
||||||
|
EXPECT_EQ(fmt::format("{}", c), "0");
|
||||||
|
EXPECT_EQ(fmt::format("{}", d), "4294967295");
|
||||||
|
|
||||||
|
static_assert(fmt::is_formattable<bitint<64>, char>{}, "");
|
||||||
|
static_assert(fmt::is_formattable<ubitint<64>, char>{}, "");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_byte
|
||||||
|
TEST(base_test, format_byte) {
|
||||||
|
auto s = std::string();
|
||||||
|
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
|
||||||
|
EXPECT_EQ(s, "42");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Only defined after the test case.
|
||||||
|
struct incomplete_type;
|
||||||
|
extern const incomplete_type& external_instance;
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<incomplete_type> : formatter<int> {
|
||||||
|
auto format(const incomplete_type& x, context& ctx) const -> appender;
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(incomplete_type_test, format) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", external_instance), "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct incomplete_type {};
|
||||||
|
const incomplete_type& external_instance = {};
|
||||||
|
|
||||||
|
auto fmt::formatter<incomplete_type>::format(const incomplete_type&,
|
||||||
|
fmt::context& ctx) const
|
||||||
|
-> fmt::appender {
|
||||||
|
return formatter<int>::format(42, ctx);
|
||||||
|
}
|
||||||
|
|||||||
@ -12,10 +12,10 @@ void invoke_inner(fmt::string_view format_str, Rep rep) {
|
|||||||
auto value = std::chrono::duration<Rep, Period>(rep);
|
auto value = std::chrono::duration<Rep, Period>(rep);
|
||||||
try {
|
try {
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||||
std::string message = fmt::format(format_str, value);
|
std::string message = fmt::format(fmt::runtime(format_str), value);
|
||||||
#else
|
#else
|
||||||
auto buf = fmt::memory_buffer();
|
auto buf = fmt::memory_buffer();
|
||||||
fmt::format_to(std::back_inserter(buf), format_str, value);
|
fmt::format_to(std::back_inserter(buf), fmt::runtime(format_str), value);
|
||||||
#endif
|
#endif
|
||||||
} catch (std::exception&) {
|
} catch (std::exception&) {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
||||||
|
|
||||||
// The size of the largest possible type in use.
|
// The size of the largest possible type in use.
|
||||||
// To let the the fuzzer mutation be efficient at cross pollinating between
|
// To let the fuzzer mutation be efficient at cross pollinating between
|
||||||
// different types, use a fixed size format. The same bit pattern, interpreted
|
// different types, use a fixed size format. The same bit pattern, interpreted
|
||||||
// as another type, is likely interesting.
|
// as another type, is likely interesting.
|
||||||
constexpr auto fixed_size = 16;
|
constexpr auto fixed_size = 16;
|
||||||
|
|||||||
@ -15,6 +15,10 @@
|
|||||||
#include "fmt/os.h"
|
#include "fmt/os.h"
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# include <crtdbg.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
||||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||||
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
|
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
|
||||||
|
|||||||
@ -13360,6 +13360,7 @@ bool UnorderedElementsAreMatcherImplBase::FindPairing(
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
@ -13983,46 +13984,50 @@ MockObjectRegistry g_mock_object_registry;
|
|||||||
|
|
||||||
// Maps a mock object to the reaction Google Mock should have when an
|
// Maps a mock object to the reaction Google Mock should have when an
|
||||||
// uninteresting method is called. Protected by g_gmock_mutex.
|
// uninteresting method is called. Protected by g_gmock_mutex.
|
||||||
std::map<const void*, internal::CallReaction> g_uninteresting_call_reaction;
|
std::unordered_map<uintptr_t, internal::CallReaction>&
|
||||||
|
UninterestingCallReactionMap() {
|
||||||
|
static auto* map = new std::unordered_map<uintptr_t, internal::CallReaction>;
|
||||||
|
return *map;
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the reaction Google Mock should have when an uninteresting
|
// Sets the reaction Google Mock should have when an uninteresting
|
||||||
// method of the given mock object is called.
|
// method of the given mock object is called.
|
||||||
void SetReactionOnUninterestingCalls(const void* mock_obj,
|
void SetReactionOnUninterestingCalls(uintptr_t mock_obj,
|
||||||
internal::CallReaction reaction)
|
internal::CallReaction reaction)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
||||||
internal::MutexLock l(&internal::g_gmock_mutex);
|
internal::MutexLock l(&internal::g_gmock_mutex);
|
||||||
g_uninteresting_call_reaction[mock_obj] = reaction;
|
UninterestingCallReactionMap()[mock_obj] = reaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// Tells Google Mock to allow uninteresting calls on the given mock
|
// Tells Google Mock to allow uninteresting calls on the given mock
|
||||||
// object.
|
// object.
|
||||||
void Mock::AllowUninterestingCalls(const void* mock_obj)
|
void Mock::AllowUninterestingCalls(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
||||||
SetReactionOnUninterestingCalls(mock_obj, internal::kAllow);
|
SetReactionOnUninterestingCalls(mock_obj, internal::kAllow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tells Google Mock to warn the user about uninteresting calls on the
|
// Tells Google Mock to warn the user about uninteresting calls on the
|
||||||
// given mock object.
|
// given mock object.
|
||||||
void Mock::WarnUninterestingCalls(const void* mock_obj)
|
void Mock::WarnUninterestingCalls(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
||||||
SetReactionOnUninterestingCalls(mock_obj, internal::kWarn);
|
SetReactionOnUninterestingCalls(mock_obj, internal::kWarn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tells Google Mock to fail uninteresting calls on the given mock
|
// Tells Google Mock to fail uninteresting calls on the given mock
|
||||||
// object.
|
// object.
|
||||||
void Mock::FailUninterestingCalls(const void* mock_obj)
|
void Mock::FailUninterestingCalls(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
||||||
SetReactionOnUninterestingCalls(mock_obj, internal::kFail);
|
SetReactionOnUninterestingCalls(mock_obj, internal::kFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tells Google Mock the given mock object is being destroyed and its
|
// Tells Google Mock the given mock object is being destroyed and its
|
||||||
// entry in the call-reaction table should be removed.
|
// entry in the call-reaction table should be removed.
|
||||||
void Mock::UnregisterCallReaction(const void* mock_obj)
|
void Mock::UnregisterCallReaction(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
||||||
internal::MutexLock l(&internal::g_gmock_mutex);
|
internal::MutexLock l(&internal::g_gmock_mutex);
|
||||||
g_uninteresting_call_reaction.erase(mock_obj);
|
UninterestingCallReactionMap().erase(static_cast<uintptr_t>(mock_obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the reaction Google Mock will have on uninteresting calls
|
// Returns the reaction Google Mock will have on uninteresting calls
|
||||||
@ -14031,9 +14036,12 @@ internal::CallReaction Mock::GetReactionOnUninterestingCalls(
|
|||||||
const void* mock_obj)
|
const void* mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
|
||||||
internal::MutexLock l(&internal::g_gmock_mutex);
|
internal::MutexLock l(&internal::g_gmock_mutex);
|
||||||
return (g_uninteresting_call_reaction.count(mock_obj) == 0) ?
|
return (UninterestingCallReactionMap().count(
|
||||||
internal::intToCallReaction(GMOCK_FLAG(default_mock_behavior)) :
|
reinterpret_cast<uintptr_t>(mock_obj)) == 0)
|
||||||
g_uninteresting_call_reaction[mock_obj];
|
? internal::intToCallReaction(
|
||||||
|
GMOCK_FLAG(default_mock_behavior))
|
||||||
|
: UninterestingCallReactionMap()[reinterpret_cast<uintptr_t>(
|
||||||
|
mock_obj)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tells Google Mock to ignore mock_obj when checking for leaked mock
|
// Tells Google Mock to ignore mock_obj when checking for leaked mock
|
||||||
|
|||||||
@ -2860,6 +2860,7 @@ GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
|||||||
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
|
||||||
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -8646,22 +8647,22 @@ class GTEST_API_ Mock {
|
|||||||
|
|
||||||
// Tells Google Mock to allow uninteresting calls on the given mock
|
// Tells Google Mock to allow uninteresting calls on the given mock
|
||||||
// object.
|
// object.
|
||||||
static void AllowUninterestingCalls(const void* mock_obj)
|
static void AllowUninterestingCalls(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
||||||
|
|
||||||
// Tells Google Mock to warn the user about uninteresting calls on
|
// Tells Google Mock to warn the user about uninteresting calls on
|
||||||
// the given mock object.
|
// the given mock object.
|
||||||
static void WarnUninterestingCalls(const void* mock_obj)
|
static void WarnUninterestingCalls(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
||||||
|
|
||||||
// Tells Google Mock to fail uninteresting calls on the given mock
|
// Tells Google Mock to fail uninteresting calls on the given mock
|
||||||
// object.
|
// object.
|
||||||
static void FailUninterestingCalls(const void* mock_obj)
|
static void FailUninterestingCalls(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
||||||
|
|
||||||
// Tells Google Mock the given mock object is being destroyed and
|
// Tells Google Mock the given mock object is being destroyed and
|
||||||
// its entry in the call-reaction table should be removed.
|
// its entry in the call-reaction table should be removed.
|
||||||
static void UnregisterCallReaction(const void* mock_obj)
|
static void UnregisterCallReaction(uintptr_t mock_obj)
|
||||||
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
|
||||||
|
|
||||||
// Returns the reaction Google Mock will have on uninteresting calls
|
// Returns the reaction Google Mock will have on uninteresting calls
|
||||||
@ -11417,6 +11418,7 @@ MATCHER(IsFalse, negation ? "is true" : "is false") {
|
|||||||
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
||||||
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
|
||||||
@ -11461,25 +11463,37 @@ constexpr bool HasStrictnessModifier() {
|
|||||||
template <typename Base>
|
template <typename Base>
|
||||||
class NiceMockImpl {
|
class NiceMockImpl {
|
||||||
public:
|
public:
|
||||||
NiceMockImpl() { ::testing::Mock::AllowUninterestingCalls(this); }
|
NiceMockImpl() {
|
||||||
|
::testing::Mock::AllowUninterestingCalls(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
|
||||||
~NiceMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
|
~NiceMockImpl() {
|
||||||
|
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Base>
|
template <typename Base>
|
||||||
class NaggyMockImpl {
|
class NaggyMockImpl {
|
||||||
public:
|
public:
|
||||||
NaggyMockImpl() { ::testing::Mock::WarnUninterestingCalls(this); }
|
NaggyMockImpl() {
|
||||||
|
::testing::Mock::WarnUninterestingCalls(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
|
||||||
~NaggyMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
|
~NaggyMockImpl() {
|
||||||
|
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Base>
|
template <typename Base>
|
||||||
class StrictMockImpl {
|
class StrictMockImpl {
|
||||||
public:
|
public:
|
||||||
StrictMockImpl() { ::testing::Mock::FailUninterestingCalls(this); }
|
StrictMockImpl() {
|
||||||
|
::testing::Mock::FailUninterestingCalls(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
|
||||||
~StrictMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
|
~StrictMockImpl() {
|
||||||
|
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|||||||
@ -6390,17 +6390,18 @@ class MatcherBase : private MatcherDescriberInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MatcherBase() : vtable_(nullptr) {}
|
MatcherBase() : vtable_(nullptr), buffer_() {}
|
||||||
|
|
||||||
// Constructs a matcher from its implementation.
|
// Constructs a matcher from its implementation.
|
||||||
template <typename U>
|
template <typename U>
|
||||||
explicit MatcherBase(const MatcherInterface<U>* impl) {
|
explicit MatcherBase(const MatcherInterface<U>* impl)
|
||||||
|
: vtable_(nullptr), buffer_() {
|
||||||
Init(impl);
|
Init(impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename M, typename = typename std::remove_reference<
|
template <typename M, typename = typename std::remove_reference<
|
||||||
M>::type::is_gtest_matcher>
|
M>::type::is_gtest_matcher>
|
||||||
MatcherBase(M&& m) { // NOLINT
|
MatcherBase(M&& m) : vtable_(nullptr), buffer_() { // NOLINT
|
||||||
Init(std::forward<M>(m));
|
Init(std::forward<M>(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ template <typename T> class mock_allocator {
|
|||||||
MOCK_METHOD(void, deallocate, (T*, size_t));
|
MOCK_METHOD(void, deallocate, (T*, size_t));
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Allocator> class allocator_ref {
|
template <typename Allocator, bool PropagateOnMove = true> class allocator_ref {
|
||||||
private:
|
private:
|
||||||
Allocator* alloc_;
|
Allocator* alloc_;
|
||||||
|
|
||||||
@ -48,6 +48,8 @@ template <typename Allocator> class allocator_ref {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
using value_type = typename Allocator::value_type;
|
using value_type = typename Allocator::value_type;
|
||||||
|
using propagate_on_container_move_assignment =
|
||||||
|
fmt::bool_constant<PropagateOnMove>;
|
||||||
|
|
||||||
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
|
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
|
||||||
|
|
||||||
@ -66,12 +68,21 @@ template <typename Allocator> class allocator_ref {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Allocator* get() const { return alloc_; }
|
auto get() const -> Allocator* { return alloc_; }
|
||||||
|
|
||||||
value_type* allocate(size_t n) {
|
auto allocate(size_t n) -> value_type* {
|
||||||
return std::allocator_traits<Allocator>::allocate(*alloc_, n);
|
return std::allocator_traits<Allocator>::allocate(*alloc_, n);
|
||||||
}
|
}
|
||||||
void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
|
void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
|
||||||
|
|
||||||
|
friend auto operator==(allocator_ref a, allocator_ref b) noexcept -> bool {
|
||||||
|
if (a.alloc_ == b.alloc_) return true;
|
||||||
|
return a.alloc_ && b.alloc_ && *a.alloc_ == *b.alloc_;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend auto operator!=(allocator_ref a, allocator_ref b) noexcept -> bool {
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // FMT_MOCK_ALLOCATOR_H_
|
#endif // FMT_MOCK_ALLOCATOR_H_
|
||||||
|
|||||||
25
test/no-builtin-types-test.cc
Normal file
25
test/no-builtin-types-test.cc
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Formatting library for C++ - formatting library tests
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#if !defined(__GNUC__) || __GNUC__ >= 5
|
||||||
|
# define FMT_BUILTIN_TYPES 0
|
||||||
|
# include "fmt/format.h"
|
||||||
|
|
||||||
|
TEST(no_builtin_types_test, format) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", 42), "42");
|
||||||
|
EXPECT_EQ(fmt::format("{}", 42L), "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(no_builtin_types_test, double_is_custom_type) {
|
||||||
|
double d = 42;
|
||||||
|
auto args = fmt::make_format_args(d);
|
||||||
|
EXPECT_EQ(fmt::format_args(args).get(0).type(),
|
||||||
|
fmt::detail::type::custom_type);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
103
test/os-test.cc
103
test/os-test.cc
@ -10,6 +10,7 @@
|
|||||||
#include <cstdlib> // std::exit
|
#include <cstdlib> // std::exit
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@ -18,10 +19,21 @@ using fmt::buffered_file;
|
|||||||
using testing::HasSubstr;
|
using testing::HasSubstr;
|
||||||
using wstring_view = fmt::basic_string_view<wchar_t>;
|
using wstring_view = fmt::basic_string_view<wchar_t>;
|
||||||
|
|
||||||
static std::string uniq_file_name(unsigned line_number) {
|
static auto uniq_file_name(unsigned line_number) -> std::string {
|
||||||
return "test-file" + std::to_string(line_number);
|
return "test-file" + std::to_string(line_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto safe_fopen(const char* filename, const char* mode) -> FILE* {
|
||||||
|
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||||
|
// Fix MSVC warning about "unsafe" fopen.
|
||||||
|
FILE* f = nullptr;
|
||||||
|
errno = fopen_s(&f, filename, mode);
|
||||||
|
return f;
|
||||||
|
#else
|
||||||
|
return std::fopen(filename, mode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
@ -236,8 +248,7 @@ TEST(buffered_file_test, descriptor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(ostream_test, move) {
|
TEST(ostream_test, move) {
|
||||||
auto test_file = uniq_file_name(__LINE__);
|
fmt::ostream out = fmt::output_file(uniq_file_name(__LINE__));
|
||||||
fmt::ostream out = fmt::output_file(test_file);
|
|
||||||
fmt::ostream moved(std::move(out));
|
fmt::ostream moved(std::move(out));
|
||||||
moved.print("hello");
|
moved.print("hello");
|
||||||
}
|
}
|
||||||
@ -429,8 +440,7 @@ TEST(file_test, read) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(file_test, read_error) {
|
TEST(file_test, read_error) {
|
||||||
auto test_file = uniq_file_name(__LINE__);
|
file f(uniq_file_name(__LINE__), file::WRONLY | file::CREATE);
|
||||||
file f(test_file, file::WRONLY | file::CREATE);
|
|
||||||
char buf;
|
char buf;
|
||||||
// We intentionally read from a file opened in the write-only mode to
|
// We intentionally read from a file opened in the write-only mode to
|
||||||
// cause error.
|
// cause error.
|
||||||
@ -439,15 +449,13 @@ TEST(file_test, read_error) {
|
|||||||
|
|
||||||
TEST(file_test, write) {
|
TEST(file_test, write) {
|
||||||
auto pipe = fmt::pipe();
|
auto pipe = fmt::pipe();
|
||||||
auto test_file = uniq_file_name(__LINE__);
|
write(pipe.write_end, "test");
|
||||||
write(pipe.write_end, test_file);
|
|
||||||
pipe.write_end.close();
|
pipe.write_end.close();
|
||||||
EXPECT_READ(pipe.read_end, test_file);
|
EXPECT_READ(pipe.read_end, "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(file_test, write_error) {
|
TEST(file_test, write_error) {
|
||||||
auto test_file = uniq_file_name(__LINE__);
|
file f(uniq_file_name(__LINE__), file::RDONLY | file::CREATE);
|
||||||
file f(test_file, file::RDONLY | file::CREATE);
|
|
||||||
// We intentionally write to a file opened in the read-only mode to
|
// We intentionally write to a file opened in the read-only mode to
|
||||||
// cause error.
|
// cause error.
|
||||||
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
|
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
|
||||||
@ -513,4 +521,79 @@ TEST(file_test, fdopen) {
|
|||||||
int read_fd = pipe.read_end.descriptor();
|
int read_fd = pipe.read_end.descriptor();
|
||||||
EXPECT_EQ(read_fd, FMT_POSIX(fileno(pipe.read_end.fdopen("r").get())));
|
EXPECT_EQ(read_fd, FMT_POSIX(fileno(pipe.read_end.fdopen("r").get())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Windows CRT implements _IOLBF incorrectly (full buffering).
|
||||||
|
# ifndef _WIN32
|
||||||
|
TEST(file_test, line_buffering) {
|
||||||
|
auto pipe = fmt::pipe();
|
||||||
|
|
||||||
|
int write_fd = pipe.write_end.descriptor();
|
||||||
|
auto write_end = pipe.write_end.fdopen("w");
|
||||||
|
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
|
||||||
|
write_end.print("42\n");
|
||||||
|
close(write_fd);
|
||||||
|
try {
|
||||||
|
write_end.close();
|
||||||
|
} catch (const std::system_error&) {
|
||||||
|
}
|
||||||
|
|
||||||
|
auto read_end = pipe.read_end.fdopen("r");
|
||||||
|
std::thread reader([&]() {
|
||||||
|
int n = 0;
|
||||||
|
int result = fscanf(read_end.get(), "%d", &n);
|
||||||
|
(void)result;
|
||||||
|
EXPECT_EQ(n, 42);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.join();
|
||||||
|
}
|
||||||
|
# endif // _WIN32
|
||||||
|
|
||||||
|
TEST(file_test, buffer_boundary) {
|
||||||
|
auto pipe = fmt::pipe();
|
||||||
|
|
||||||
|
auto write_end = pipe.write_end.fdopen("w");
|
||||||
|
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
|
||||||
|
for (int i = 3; i < 4094; i++)
|
||||||
|
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
|
||||||
|
write_end.print("{} {}", 1234, 567);
|
||||||
|
write_end.close();
|
||||||
|
|
||||||
|
auto read_end = pipe.read_end.fdopen("r");
|
||||||
|
char buf[4091] = {};
|
||||||
|
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
|
||||||
|
EXPECT_EQ(n, sizeof(buf));
|
||||||
|
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(file_test, io_putting) {
|
||||||
|
auto pipe = fmt::pipe();
|
||||||
|
auto read_end = pipe.read_end.fdopen("r");
|
||||||
|
auto write_end = pipe.write_end.fdopen("w");
|
||||||
|
|
||||||
|
size_t read_size = 0;
|
||||||
|
auto reader = std::thread([&]() {
|
||||||
|
size_t n = 0;
|
||||||
|
do {
|
||||||
|
char buf[4096] = {};
|
||||||
|
n = fread(buf, 1, sizeof(buf), read_end.get());
|
||||||
|
read_size += n;
|
||||||
|
} while (n != 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// This initialize buffers but doesn't set _IO_CURRENTLY_PUTTING.
|
||||||
|
fseek(write_end.get(), 0, SEEK_SET);
|
||||||
|
|
||||||
|
size_t write_size = 0;
|
||||||
|
for (int i = 0; i <= 20000; ++i) {
|
||||||
|
auto s = fmt::format("{}\n", i);
|
||||||
|
fmt::print(write_end.get(), "{}", s);
|
||||||
|
write_size += s.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
write_end.close();
|
||||||
|
reader.join();
|
||||||
|
EXPECT_EQ(read_size, write_size);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
|
|||||||
@ -265,7 +265,7 @@ template <> struct formatter<abstract> : ostream_formatter {};
|
|||||||
} // namespace fmt
|
} // namespace fmt
|
||||||
|
|
||||||
void format_abstract_compiles(const abstract& a) {
|
void format_abstract_compiles(const abstract& a) {
|
||||||
fmt::format(FMT_COMPILE("{}"), a);
|
(void)fmt::format(FMT_COMPILE("{}"), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ostream_test, is_formattable) {
|
TEST(ostream_test, is_formattable) {
|
||||||
|
|||||||
25
test/perf-sanity.cc
Normal file
25
test/perf-sanity.cc
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// A quick and dirty performance test.
|
||||||
|
// For actual benchmarks see https://github.com/fmtlib/format-benchmark.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const int n = 10000000;
|
||||||
|
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
for (int iteration = 0; iteration < n; ++iteration) {
|
||||||
|
auto buf = fmt::memory_buffer();
|
||||||
|
fmt::format_to(std::back_inserter(buf),
|
||||||
|
"Hello, {}. The answer is {} and {}.", 1, 2345, 6789);
|
||||||
|
}
|
||||||
|
std::atomic_signal_fence(std::memory_order_acq_rel); // Clobber memory.
|
||||||
|
auto end = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// Print time in milliseconds.
|
||||||
|
std::chrono::duration<double> duration = end - start;
|
||||||
|
fmt::print("{:.1f}\n", duration.count() * 1000);
|
||||||
|
}
|
||||||
@ -6,16 +6,12 @@
|
|||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#include "fmt/printf.h"
|
#include "fmt/printf.h"
|
||||||
// include <format> if possible for https://github.com/fmtlib/fmt/pull/4042
|
|
||||||
#if FMT_HAS_INCLUDE(<format>) && FMT_CPLUSPLUS > 201703L
|
|
||||||
# include <format>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "fmt/xchar.h"
|
#include "fmt/xchar.h" // DEPRECATED!
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
@ -26,27 +22,21 @@ using fmt::detail::max_value;
|
|||||||
const unsigned big_num = INT_MAX + 1u;
|
const unsigned big_num = INT_MAX + 1u;
|
||||||
|
|
||||||
// Makes format string argument positional.
|
// Makes format string argument positional.
|
||||||
static std::string make_positional(fmt::string_view format) {
|
static auto make_positional(fmt::string_view format) -> std::string {
|
||||||
std::string s(format.data(), format.size());
|
std::string s(format.data(), format.size());
|
||||||
s.replace(s.find('%'), 1, "%1$");
|
s.replace(s.find('%'), 1, "%1$");
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::wstring make_positional(fmt::basic_string_view<wchar_t> format) {
|
|
||||||
std::wstring s(format.data(), format.size());
|
|
||||||
s.replace(s.find(L'%'), 1, L"%1$");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A wrapper around fmt::sprintf to workaround bogus warnings about invalid
|
// A wrapper around fmt::sprintf to workaround bogus warnings about invalid
|
||||||
// format strings in MSVC.
|
// format strings in MSVC.
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
std::string test_sprintf(fmt::string_view format, const Args&... args) {
|
auto test_sprintf(fmt::string_view format, const Args&... args) -> std::string {
|
||||||
return fmt::sprintf(format, args...);
|
return fmt::sprintf(format, args...);
|
||||||
}
|
}
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
|
auto test_sprintf(fmt::basic_string_view<wchar_t> format, const Args&... args)
|
||||||
const Args&... args) {
|
-> std::wstring {
|
||||||
return fmt::sprintf(format, args...);
|
return fmt::sprintf(format, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,10 +45,7 @@ std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
|
|||||||
<< "format: " << format; \
|
<< "format: " << format; \
|
||||||
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
|
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
|
||||||
|
|
||||||
TEST(printf_test, no_args) {
|
TEST(printf_test, no_args) { EXPECT_EQ("test", test_sprintf("test")); }
|
||||||
EXPECT_EQ("test", test_sprintf("test"));
|
|
||||||
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(printf_test, escape) {
|
TEST(printf_test, escape) {
|
||||||
EXPECT_EQ("%", test_sprintf("%%"));
|
EXPECT_EQ("%", test_sprintf("%%"));
|
||||||
@ -66,11 +53,6 @@ TEST(printf_test, escape) {
|
|||||||
EXPECT_EQ("% after", test_sprintf("%% after"));
|
EXPECT_EQ("% after", test_sprintf("%% after"));
|
||||||
EXPECT_EQ("before % after", test_sprintf("before %% after"));
|
EXPECT_EQ("before % after", test_sprintf("before %% after"));
|
||||||
EXPECT_EQ("%s", test_sprintf("%%s"));
|
EXPECT_EQ("%s", test_sprintf("%%s"));
|
||||||
EXPECT_EQ(L"%", fmt::sprintf(L"%%"));
|
|
||||||
EXPECT_EQ(L"before %", fmt::sprintf(L"before %%"));
|
|
||||||
EXPECT_EQ(L"% after", fmt::sprintf(L"%% after"));
|
|
||||||
EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after"));
|
|
||||||
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(printf_test, positional_args) {
|
TEST(printf_test, positional_args) {
|
||||||
@ -467,9 +449,6 @@ TEST(printf_test, char) {
|
|||||||
EXPECT_PRINTF("x", "%c", 'x');
|
EXPECT_PRINTF("x", "%c", 'x');
|
||||||
int max = max_value<int>();
|
int max = max_value<int>();
|
||||||
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
||||||
// EXPECT_PRINTF("x", "%lc", L'x');
|
|
||||||
EXPECT_PRINTF(L"x", L"%c", L'x');
|
|
||||||
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(printf_test, string) {
|
TEST(printf_test, string) {
|
||||||
@ -477,10 +456,6 @@ TEST(printf_test, string) {
|
|||||||
const char* null_str = nullptr;
|
const char* null_str = nullptr;
|
||||||
EXPECT_PRINTF("(null)", "%s", null_str);
|
EXPECT_PRINTF("(null)", "%s", null_str);
|
||||||
EXPECT_PRINTF(" (null)", "%10s", null_str);
|
EXPECT_PRINTF(" (null)", "%10s", null_str);
|
||||||
EXPECT_PRINTF(L"abc", L"%s", L"abc");
|
|
||||||
const wchar_t* null_wstr = nullptr;
|
|
||||||
EXPECT_PRINTF(L"(null)", L"%s", null_wstr);
|
|
||||||
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(printf_test, pointer) {
|
TEST(printf_test, pointer) {
|
||||||
@ -494,16 +469,6 @@ TEST(printf_test, pointer) {
|
|||||||
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
|
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
|
||||||
const char* null_str = nullptr;
|
const char* null_str = nullptr;
|
||||||
EXPECT_PRINTF("(nil)", "%p", null_str);
|
EXPECT_PRINTF("(nil)", "%p", null_str);
|
||||||
|
|
||||||
p = &n;
|
|
||||||
EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p);
|
|
||||||
p = nullptr;
|
|
||||||
EXPECT_PRINTF(L"(nil)", L"%p", p);
|
|
||||||
EXPECT_PRINTF(L" (nil)", L"%10p", p);
|
|
||||||
const wchar_t* w = L"test";
|
|
||||||
EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w);
|
|
||||||
const wchar_t* null_wstr = nullptr;
|
|
||||||
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum test_enum { answer = 42 };
|
enum test_enum { answer = 42 };
|
||||||
@ -531,10 +496,6 @@ TEST(printf_test, printf_error) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(printf_test, wide_string) {
|
|
||||||
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(printf_test, vprintf) {
|
TEST(printf_test, vprintf) {
|
||||||
int n = 42;
|
int n = 42;
|
||||||
auto store = fmt::make_format_args<fmt::printf_context>(n);
|
auto store = fmt::make_format_args<fmt::printf_context>(n);
|
||||||
|
|||||||
@ -28,11 +28,6 @@
|
|||||||
# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1910
|
|
||||||
# define FMT_RANGES_TEST_ENABLE_JOIN
|
|
||||||
# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
TEST(ranges_test, format_array) {
|
TEST(ranges_test, format_array) {
|
||||||
int arr[] = {1, 2, 3, 5, 7, 11};
|
int arr[] = {1, 2, 3, 5, 7, 11};
|
||||||
@ -52,6 +47,8 @@ TEST(ranges_test, format_array_of_literals) {
|
|||||||
}
|
}
|
||||||
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
|
|
||||||
|
struct unformattable {};
|
||||||
|
|
||||||
TEST(ranges_test, format_vector) {
|
TEST(ranges_test, format_vector) {
|
||||||
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
||||||
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
||||||
@ -70,6 +67,9 @@ TEST(ranges_test, format_vector) {
|
|||||||
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
|
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
|
||||||
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
|
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
|
||||||
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
|
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
|
||||||
|
|
||||||
|
EXPECT_FALSE(fmt::is_formattable<unformattable>::value);
|
||||||
|
EXPECT_FALSE(fmt::is_formattable<std::vector<unformattable>>::value);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, format_nested_vector) {
|
TEST(ranges_test, format_nested_vector) {
|
||||||
@ -88,6 +88,8 @@ TEST(ranges_test, format_map) {
|
|||||||
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
|
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
|
||||||
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
|
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
|
||||||
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
|
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
|
||||||
|
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<std::map<int, unformattable>>::value));
|
||||||
}
|
}
|
||||||
|
|
||||||
struct test_map_value {};
|
struct test_map_value {};
|
||||||
@ -151,6 +153,7 @@ template <typename T> class flat_set {
|
|||||||
TEST(ranges_test, format_flat_set) {
|
TEST(ranges_test, format_flat_set) {
|
||||||
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
|
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
|
||||||
"{\"one\", \"two\"}");
|
"{\"one\", \"two\"}");
|
||||||
|
EXPECT_FALSE(fmt::is_formattable<flat_set<unformattable>>::value);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace adl {
|
namespace adl {
|
||||||
@ -170,19 +173,19 @@ TEST(ranges_test, format_adl_begin_end) {
|
|||||||
TEST(ranges_test, format_pair) {
|
TEST(ranges_test, format_pair) {
|
||||||
auto p = std::pair<int, float>(42, 1.5f);
|
auto p = std::pair<int, float>(42, 1.5f);
|
||||||
EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
|
EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
|
||||||
|
EXPECT_EQ(fmt::format("{:}", p), "(42, 1.5)");
|
||||||
|
EXPECT_EQ(fmt::format("{:n}", p), "421.5");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct unformattable {};
|
|
||||||
|
|
||||||
TEST(ranges_test, format_tuple) {
|
TEST(ranges_test, format_tuple) {
|
||||||
auto t =
|
auto t =
|
||||||
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
|
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
|
||||||
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
|
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
|
||||||
|
EXPECT_EQ(fmt::format("{:n}", t), "421.5\"this is tuple\"'i'");
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
|
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
|
||||||
|
|
||||||
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
|
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
|
||||||
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
|
||||||
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
|
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
|
||||||
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
|
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
|
||||||
EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
|
EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
|
||||||
@ -210,7 +213,6 @@ TEST(ranges_test, tuple_parse_calls_element_parse) {
|
|||||||
EXPECT_THROW(f.parse(ctx), bad_format);
|
EXPECT_THROW(f.parse(ctx), bad_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
|
||||||
struct tuple_like {
|
struct tuple_like {
|
||||||
int i;
|
int i;
|
||||||
std::string str;
|
std::string str;
|
||||||
@ -230,9 +232,12 @@ auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
|
|||||||
return t.get<N>();
|
return t.get<N>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/llvm/llvm-project/issues/39218
|
||||||
|
FMT_PRAGMA_CLANG(diagnostic ignored "-Wmismatched-tags")
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
template <>
|
template <>
|
||||||
struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {};
|
struct tuple_size<tuple_like> : public std::integral_constant<size_t, 2> {};
|
||||||
|
|
||||||
template <size_t N> struct tuple_element<N, tuple_like> {
|
template <size_t N> struct tuple_element<N, tuple_like> {
|
||||||
using type = decltype(std::declval<tuple_like>().get<N>());
|
using type = decltype(std::declval<tuple_like>().get<N>());
|
||||||
@ -243,7 +248,6 @@ TEST(ranges_test, format_struct) {
|
|||||||
auto t = tuple_like{42, "foo"};
|
auto t = tuple_like{42, "foo"};
|
||||||
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
|
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
|
||||||
}
|
}
|
||||||
#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
|
||||||
|
|
||||||
TEST(ranges_test, format_to) {
|
TEST(ranges_test, format_to) {
|
||||||
char buf[10];
|
char buf[10];
|
||||||
@ -326,7 +330,7 @@ template <typename T> class noncopyable_range {
|
|||||||
explicit noncopyable_range(Args&&... args)
|
explicit noncopyable_range(Args&&... args)
|
||||||
: vec(std::forward<Args>(args)...) {}
|
: vec(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
noncopyable_range(noncopyable_range const&) = delete;
|
noncopyable_range(const noncopyable_range&) = delete;
|
||||||
noncopyable_range(noncopyable_range&) = delete;
|
noncopyable_range(noncopyable_range&) = delete;
|
||||||
|
|
||||||
auto begin() -> iterator { return vec.begin(); }
|
auto begin() -> iterator { return vec.begin(); }
|
||||||
@ -360,8 +364,7 @@ TEST(ranges_test, enum_range) {
|
|||||||
|
|
||||||
#if !FMT_MSC_VERSION
|
#if !FMT_MSC_VERSION
|
||||||
TEST(ranges_test, unformattable_range) {
|
TEST(ranges_test, unformattable_range) {
|
||||||
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
|
EXPECT_FALSE((fmt::is_formattable<std::vector<unformattable>, char>::value));
|
||||||
fmt::format_context>::value));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -399,7 +402,6 @@ TEST(ranges_test, join_bytes) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef FMT_RANGES_TEST_ENABLE_JOIN
|
|
||||||
TEST(ranges_test, join_tuple) {
|
TEST(ranges_test, join_tuple) {
|
||||||
// Value tuple args.
|
// Value tuple args.
|
||||||
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
|
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
|
||||||
@ -418,7 +420,11 @@ TEST(ranges_test, join_tuple) {
|
|||||||
auto t4 = std::tuple<float>(4.0f);
|
auto t4 = std::tuple<float>(4.0f);
|
||||||
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
|
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
|
||||||
|
|
||||||
# if FMT_TUPLE_JOIN_SPECIFIERS
|
// Tuple-like.
|
||||||
|
auto t5 = tuple_like{42, "foo"};
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo");
|
||||||
|
|
||||||
|
#if FMT_TUPLE_JOIN_SPECIFIERS
|
||||||
// Specs applied to each element.
|
// Specs applied to each element.
|
||||||
auto t5 = std::tuple<int, int, long>(-3, 100, 1);
|
auto t5 = std::tuple<int, int, long>(-3, 100, 1);
|
||||||
EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01");
|
EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01");
|
||||||
@ -431,7 +437,7 @@ TEST(ranges_test, join_tuple) {
|
|||||||
int y = -1;
|
int y = -1;
|
||||||
auto t7 = std::tuple<int, int&, const int&>(3, y, y);
|
auto t7 = std::tuple<int, int&, const int&>(3, y, y);
|
||||||
EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01");
|
EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01");
|
||||||
# endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, join_initializer_list) {
|
TEST(ranges_test, join_initializer_list) {
|
||||||
@ -451,7 +457,7 @@ struct zstring {
|
|||||||
auto end() const -> zstring_sentinel { return {}; }
|
auto end() const -> zstring_sentinel { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
# ifdef __cpp_lib_ranges
|
#ifdef __cpp_lib_ranges
|
||||||
struct cpp20_only_range {
|
struct cpp20_only_range {
|
||||||
struct iterator {
|
struct iterator {
|
||||||
int val = 0;
|
int val = 0;
|
||||||
@ -481,7 +487,7 @@ struct cpp20_only_range {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static_assert(std::input_iterator<cpp20_only_range::iterator>);
|
static_assert(std::input_iterator<cpp20_only_range::iterator>);
|
||||||
# endif
|
#endif
|
||||||
|
|
||||||
TEST(ranges_test, join_sentinel) {
|
TEST(ranges_test, join_sentinel) {
|
||||||
auto hello = zstring{"hello"};
|
auto hello = zstring{"hello"};
|
||||||
@ -509,13 +515,13 @@ TEST(ranges_test, join_range) {
|
|||||||
const auto z = std::vector<int>(3u, 0);
|
const auto z = std::vector<int>(3u, 0);
|
||||||
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
|
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
|
||||||
|
|
||||||
# ifdef __cpp_lib_ranges
|
#ifdef __cpp_lib_ranges
|
||||||
EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}),
|
EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}),
|
||||||
"[0, 1, 2, 3, 4]");
|
"[0, 1, 2, 3, 4]");
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")),
|
fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")),
|
||||||
"0,1,2,3,4");
|
"0,1,2,3,4");
|
||||||
# endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace adl {
|
namespace adl {
|
||||||
@ -531,8 +537,6 @@ TEST(ranges_test, format_join_adl_begin_end) {
|
|||||||
EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43");
|
EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_RANGES_TEST_ENABLE_JOIN
|
|
||||||
|
|
||||||
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L
|
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L
|
||||||
TEST(ranges_test, nested_ranges) {
|
TEST(ranges_test, nested_ranges) {
|
||||||
auto l = std::list{1, 2, 3};
|
auto l = std::list{1, 2, 3};
|
||||||
@ -558,7 +562,7 @@ TEST(ranges_test, escape) {
|
|||||||
EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]");
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]");
|
||||||
|
|
||||||
if (fmt::detail::use_utf8()) {
|
if (fmt::detail::use_utf8) {
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
|
||||||
// Unassigned Unicode code points.
|
// Unassigned Unicode code points.
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
|
||||||
@ -576,7 +580,11 @@ TEST(ranges_test, escape) {
|
|||||||
|
|
||||||
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
|
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
|
||||||
"[['x']]");
|
"[['x']]");
|
||||||
|
|
||||||
|
// Disabled due to a clang 17 bug: https://github.com/fmtlib/fmt/issues/4144.
|
||||||
|
#if FMT_CLANG_VERSION >= 1800
|
||||||
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
|
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename R> struct fmt_ref_view {
|
template <typename R> struct fmt_ref_view {
|
||||||
@ -655,6 +663,8 @@ TEST(ranges_test, container_adaptor) {
|
|||||||
m.push(2);
|
m.push(2);
|
||||||
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
|
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EXPECT_FALSE(fmt::is_formattable<std::stack<unformattable>>::value);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct tieable {
|
struct tieable {
|
||||||
@ -752,17 +762,17 @@ TEST(ranges_test, std_istream_iterator_join) {
|
|||||||
EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", fmt::join(first, last, ", ")));
|
EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", fmt::join(first, last, ", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, movable_only_istream_iter_join) {
|
// Mirrors C++20 std::ranges::basic_istream_view::iterator.
|
||||||
// Mirrors C++20 std::ranges::basic_istream_view::iterator.
|
struct noncopyable_istream_iterator : std::istream_iterator<int> {
|
||||||
struct noncopyable_istream_iterator : std::istream_iterator<int> {
|
using base = std::istream_iterator<int>;
|
||||||
explicit noncopyable_istream_iterator(std::istringstream& iss)
|
explicit noncopyable_istream_iterator(std::istringstream& iss) : base{iss} {}
|
||||||
: std::istream_iterator<int>{iss} {}
|
noncopyable_istream_iterator(const noncopyable_istream_iterator&) = delete;
|
||||||
noncopyable_istream_iterator(const noncopyable_istream_iterator&) = delete;
|
noncopyable_istream_iterator(noncopyable_istream_iterator&&) = default;
|
||||||
noncopyable_istream_iterator(noncopyable_istream_iterator&&) = default;
|
};
|
||||||
};
|
static_assert(!std::is_copy_constructible<noncopyable_istream_iterator>::value,
|
||||||
static_assert(
|
"");
|
||||||
!std::is_copy_constructible<noncopyable_istream_iterator>::value, "");
|
|
||||||
|
|
||||||
|
TEST(ranges_test, movable_only_istream_iter_join) {
|
||||||
auto&& iss = std::istringstream("1 2 3 4 5");
|
auto&& iss = std::istringstream("1 2 3 4 5");
|
||||||
auto first = noncopyable_istream_iterator(iss);
|
auto first = noncopyable_istream_iterator(iss);
|
||||||
auto last = std::istream_iterator<int>();
|
auto last = std::istream_iterator<int>();
|
||||||
@ -770,6 +780,18 @@ TEST(ranges_test, movable_only_istream_iter_join) {
|
|||||||
fmt::format("{}", fmt::join(std::move(first), last, ", ")));
|
fmt::format("{}", fmt::join(std::move(first), last, ", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct movable_iter_range {
|
||||||
|
std::istringstream iss{"1 2 3 4 5"};
|
||||||
|
noncopyable_istream_iterator begin() {
|
||||||
|
return noncopyable_istream_iterator{iss};
|
||||||
|
}
|
||||||
|
std::istream_iterator<int> end() { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ranges_test, movable_only_istream_iter_join2) {
|
||||||
|
EXPECT_EQ("[1, 2, 3, 4, 5]", fmt::format("{}", movable_iter_range{}));
|
||||||
|
}
|
||||||
|
|
||||||
struct not_range {
|
struct not_range {
|
||||||
void begin() const {}
|
void begin() const {}
|
||||||
void end() const {}
|
void end() const {}
|
||||||
|
|||||||
@ -111,9 +111,9 @@ TEST(scan_test, invalid_format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
using fmt::scan;
|
using fmt::scan;
|
||||||
using fmt::scan_error;
|
using fmt::scan_error;
|
||||||
}
|
} // namespace std
|
||||||
|
|
||||||
TEST(scan_test, example) {
|
TEST(scan_test, example) {
|
||||||
// Example from https://wg21.link/p1729r3.
|
// Example from https://wg21.link/p1729r3.
|
||||||
|
|||||||
70
test/scan.h
70
test/scan.h
@ -192,7 +192,10 @@ class file_scan_buffer final : public scan_buffer {
|
|||||||
flockfile(f);
|
flockfile(f);
|
||||||
fill();
|
fill();
|
||||||
}
|
}
|
||||||
~file_scan_buffer() { funlockfile(file_); }
|
~file_scan_buffer() {
|
||||||
|
FILE* f = file_;
|
||||||
|
funlockfile(f);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
@ -208,7 +211,7 @@ class scan_parse_context {
|
|||||||
public:
|
public:
|
||||||
using iterator = string_view::iterator;
|
using iterator = string_view::iterator;
|
||||||
|
|
||||||
explicit FMT_CONSTEXPR scan_parse_context(string_view format)
|
FMT_CONSTEXPR explicit scan_parse_context(string_view format)
|
||||||
: format_(format) {}
|
: format_(format) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
|
FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
|
||||||
@ -226,6 +229,8 @@ enum class scan_type {
|
|||||||
uint_type,
|
uint_type,
|
||||||
long_long_type,
|
long_long_type,
|
||||||
ulong_long_type,
|
ulong_long_type,
|
||||||
|
double_type,
|
||||||
|
float_type,
|
||||||
string_type,
|
string_type,
|
||||||
string_view_type,
|
string_view_type,
|
||||||
custom_type
|
custom_type
|
||||||
@ -248,6 +253,8 @@ template <typename Context> class basic_scan_arg {
|
|||||||
unsigned* uint_value_;
|
unsigned* uint_value_;
|
||||||
long long* long_long_value_;
|
long long* long_long_value_;
|
||||||
unsigned long long* ulong_long_value_;
|
unsigned long long* ulong_long_value_;
|
||||||
|
double* double_value_;
|
||||||
|
float* float_value_;
|
||||||
std::string* string_;
|
std::string* string_;
|
||||||
string_view* string_view_;
|
string_view* string_view_;
|
||||||
detail::custom_scan_arg<Context> custom_;
|
detail::custom_scan_arg<Context> custom_;
|
||||||
@ -273,6 +280,10 @@ template <typename Context> class basic_scan_arg {
|
|||||||
: type_(scan_type::long_long_type), long_long_value_(&value) {}
|
: type_(scan_type::long_long_type), long_long_value_(&value) {}
|
||||||
FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
|
FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
|
||||||
: type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
|
: type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
|
||||||
|
FMT_CONSTEXPR basic_scan_arg(double& value)
|
||||||
|
: type_(scan_type::double_type), double_value_(&value) {}
|
||||||
|
FMT_CONSTEXPR basic_scan_arg(float& value)
|
||||||
|
: type_(scan_type::float_type), float_value_(&value) {}
|
||||||
FMT_CONSTEXPR basic_scan_arg(std::string& value)
|
FMT_CONSTEXPR basic_scan_arg(std::string& value)
|
||||||
: type_(scan_type::string_type), string_(&value) {}
|
: type_(scan_type::string_type), string_(&value) {}
|
||||||
FMT_CONSTEXPR basic_scan_arg(string_view& value)
|
FMT_CONSTEXPR basic_scan_arg(string_view& value)
|
||||||
@ -302,6 +313,10 @@ template <typename Context> class basic_scan_arg {
|
|||||||
return vis(*long_long_value_);
|
return vis(*long_long_value_);
|
||||||
case scan_type::ulong_long_type:
|
case scan_type::ulong_long_type:
|
||||||
return vis(*ulong_long_value_);
|
return vis(*ulong_long_value_);
|
||||||
|
case scan_type::double_type:
|
||||||
|
return vis(*double_value_);
|
||||||
|
case scan_type::float_type:
|
||||||
|
return vis(*float_value_);
|
||||||
case scan_type::string_type:
|
case scan_type::string_type:
|
||||||
return vis(*string_);
|
return vis(*string_);
|
||||||
case scan_type::string_view_type:
|
case scan_type::string_view_type:
|
||||||
@ -344,7 +359,7 @@ class scan_context {
|
|||||||
using iterator = detail::scan_iterator;
|
using iterator = detail::scan_iterator;
|
||||||
using sentinel = detail::scan_sentinel;
|
using sentinel = detail::scan_sentinel;
|
||||||
|
|
||||||
explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args)
|
FMT_CONSTEXPR explicit scan_context(detail::scan_buffer& buf, scan_args args)
|
||||||
: buf_(buf), args_(args) {}
|
: buf_(buf), args_(args) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR auto arg(int id) const -> scan_arg {
|
FMT_CONSTEXPR auto arg(int id) const -> scan_arg {
|
||||||
@ -365,7 +380,7 @@ const char* parse_scan_specs(const char* begin, const char* end,
|
|||||||
switch (to_ascii(*begin)) {
|
switch (to_ascii(*begin)) {
|
||||||
// TODO: parse more scan format specifiers
|
// TODO: parse more scan format specifiers
|
||||||
case 'x':
|
case 'x':
|
||||||
specs.type = presentation_type::hex;
|
specs.set_type(presentation_type::hex);
|
||||||
++begin;
|
++begin;
|
||||||
break;
|
break;
|
||||||
case '}':
|
case '}':
|
||||||
@ -434,7 +449,7 @@ auto read_hex(scan_iterator it, T& value) -> scan_iterator {
|
|||||||
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
||||||
auto read(scan_iterator it, T& value, const format_specs& specs)
|
auto read(scan_iterator it, T& value, const format_specs& specs)
|
||||||
-> scan_iterator {
|
-> scan_iterator {
|
||||||
if (specs.type == presentation_type::hex) return read_hex(it, value);
|
if (specs.type() == presentation_type::hex) return read_hex(it, value);
|
||||||
return read(it, value);
|
return read(it, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,6 +469,47 @@ auto read(scan_iterator it, T& value, const format_specs& specs = {})
|
|||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto read(scan_iterator it, double& value, const format_specs& = {})
|
||||||
|
-> scan_iterator {
|
||||||
|
if (it == scan_sentinel()) return it;
|
||||||
|
|
||||||
|
// Simple floating-point parsing
|
||||||
|
bool negative = *it == '-';
|
||||||
|
if (negative) {
|
||||||
|
++it;
|
||||||
|
if (it == scan_sentinel()) report_error("invalid input");
|
||||||
|
}
|
||||||
|
|
||||||
|
double result = 0.0;
|
||||||
|
// Parse integer part
|
||||||
|
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
|
||||||
|
result = result * 10.0 + (*it - '0');
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse decimal part if present
|
||||||
|
if (it != scan_sentinel() && *it == '.') {
|
||||||
|
++it;
|
||||||
|
double fraction = 0.1;
|
||||||
|
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
|
||||||
|
result += (*it - '0') * fraction;
|
||||||
|
fraction *= 0.1;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = negative ? -result : result;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto read(scan_iterator it, float& value, const format_specs& specs = {})
|
||||||
|
-> scan_iterator {
|
||||||
|
double temp;
|
||||||
|
it = read(it, temp, specs);
|
||||||
|
value = static_cast<float>(temp);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
auto read(scan_iterator it, std::string& value, const format_specs& = {})
|
auto read(scan_iterator it, std::string& value, const format_specs& = {})
|
||||||
-> scan_iterator {
|
-> scan_iterator {
|
||||||
while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);
|
while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);
|
||||||
@ -550,12 +606,12 @@ struct scan_handler {
|
|||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_error(const char* message) { report_error(message); }
|
FMT_NORETURN void on_error(const char* message) { report_error(message); }
|
||||||
};
|
};
|
||||||
|
|
||||||
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
|
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
|
||||||
auto h = detail::scan_handler(fmt, buf, args);
|
auto h = detail::scan_handler(fmt, buf, args);
|
||||||
detail::parse_format_string<false>(fmt, h);
|
detail::parse_format_string(fmt, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <size_t I, typename... T, FMT_ENABLE_IF(I == sizeof...(T))>
|
template <size_t I, typename... T, FMT_ENABLE_IF(I == sizeof...(T))>
|
||||||
|
|||||||
132
test/std-test.cc
132
test/std-test.cc
@ -12,7 +12,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "fmt/os.h" // fmt::system_category
|
#include "fmt/os.h" // fmt::system_category
|
||||||
#include "fmt/ranges.h"
|
#include "fmt/ranges.h"
|
||||||
#include "gtest-extra.h" // StartsWith
|
#include "gtest-extra.h" // StartsWith
|
||||||
|
|
||||||
@ -20,6 +20,11 @@
|
|||||||
TEST(std_test, path) {
|
TEST(std_test, path) {
|
||||||
using std::filesystem::path;
|
using std::filesystem::path;
|
||||||
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
|
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
|
||||||
|
|
||||||
|
// see #4303
|
||||||
|
const path p = "/usr/bin";
|
||||||
|
EXPECT_EQ(fmt::format("{}", p), "/usr/bin");
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
|
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
|
||||||
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
|
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
|
||||||
|
|
||||||
@ -34,11 +39,18 @@ TEST(std_test, path) {
|
|||||||
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
|
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
|
||||||
L"\x0447\x044B\x043D\x0430")),
|
L"\x0447\x044B\x043D\x0430")),
|
||||||
"Шчучыншчына");
|
"Шчучыншчына");
|
||||||
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
|
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
|
||||||
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
|
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
|
||||||
|
"[\xED\xA0\xBD\xF0\x9F\x98\x80]");
|
||||||
|
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intentionally delayed include to test #4303
|
||||||
|
# include "fmt/ranges.h"
|
||||||
|
|
||||||
// Test ambiguity problem described in #2954.
|
// Test ambiguity problem described in #2954.
|
||||||
TEST(ranges_std_test, format_vector_path) {
|
TEST(ranges_std_test, format_vector_path) {
|
||||||
auto p = std::filesystem::path("foo/bar.txt");
|
auto p = std::filesystem::path("foo/bar.txt");
|
||||||
@ -86,6 +98,9 @@ TEST(std_test, complex) {
|
|||||||
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
|
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
|
||||||
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
|
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "(1+2i) ");
|
||||||
|
EXPECT_EQ(fmt::format("{:-<8}", std::complex<double>(1, 2)), "(1+2i)--");
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
|
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
|
||||||
" (1.00+2.20i)");
|
" (1.00+2.20i)");
|
||||||
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),
|
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),
|
||||||
@ -130,11 +145,13 @@ TEST(std_test, optional) {
|
|||||||
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
||||||
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
|
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
|
||||||
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
|
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
|
||||||
|
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(std_test, expected) {
|
TEST(std_test, expected) {
|
||||||
#ifdef __cpp_lib_expected
|
#ifdef __cpp_lib_expected
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::expected<void, int>{}), "expected()");
|
||||||
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{1}), "expected(1)");
|
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{1}), "expected(1)");
|
||||||
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{std::unexpected(1)}),
|
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{std::unexpected(1)}),
|
||||||
"unexpected(1)");
|
"unexpected(1)");
|
||||||
@ -158,6 +175,7 @@ TEST(std_test, expected) {
|
|||||||
EXPECT_FALSE(
|
EXPECT_FALSE(
|
||||||
(fmt::is_formattable<std::expected<int, unformattable2>>::value));
|
(fmt::is_formattable<std::expected<int, unformattable2>>::value));
|
||||||
EXPECT_TRUE((fmt::is_formattable<std::expected<int, int>>::value));
|
EXPECT_TRUE((fmt::is_formattable<std::expected<int, int>>::value));
|
||||||
|
EXPECT_TRUE((fmt::is_formattable<std::expected<void, int>>::value));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +197,33 @@ class my_class {
|
|||||||
return fmt::to_string(elm.av);
|
return fmt::to_string(elm.av);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class my_class_int {
|
||||||
|
public:
|
||||||
|
int av;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
|
||||||
|
};
|
||||||
} // namespace my_nso
|
} // namespace my_nso
|
||||||
|
|
||||||
|
TEST(std_test, expected_format_as) {
|
||||||
|
#ifdef __cpp_lib_expected
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format(
|
||||||
|
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
|
||||||
|
"expected(\"first\")");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{}",
|
||||||
|
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
|
||||||
|
"expected(\"7\")");
|
||||||
|
EXPECT_EQ(fmt::format("{}",
|
||||||
|
std::expected<my_nso::my_class_int, int>{
|
||||||
|
my_nso::my_class_int{8}}),
|
||||||
|
"expected(8)");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
TEST(std_test, optional_format_as) {
|
TEST(std_test, optional_format_as) {
|
||||||
#ifdef __cpp_lib_optional
|
#ifdef __cpp_lib_optional
|
||||||
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
|
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
|
||||||
@ -188,6 +232,8 @@ TEST(std_test, optional_format_as) {
|
|||||||
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
|
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
|
||||||
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
|
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
|
||||||
"optional(\"7\")");
|
"optional(\"7\")");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
|
||||||
|
"optional(8)");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,16 +303,42 @@ TEST(std_test, variant) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(std_test, variant_format_as) {
|
||||||
|
#ifdef __cpp_lib_variant
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
|
||||||
|
"variant(\"first\")");
|
||||||
|
EXPECT_EQ(fmt::format(
|
||||||
|
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
|
||||||
|
"variant(\"first\")");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
|
||||||
|
"variant(\"7\")");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{}",
|
||||||
|
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
|
||||||
|
"variant(8)");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
TEST(std_test, error_code) {
|
TEST(std_test, error_code) {
|
||||||
EXPECT_EQ("generic:42",
|
auto& generic = std::generic_category();
|
||||||
fmt::format(FMT_STRING("{0}"),
|
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
|
||||||
std::error_code(42, std::generic_category())));
|
EXPECT_EQ(fmt::format("{:>12}", std::error_code(42, generic)),
|
||||||
EXPECT_EQ("system:42",
|
" generic:42");
|
||||||
fmt::format(FMT_STRING("{0}"),
|
EXPECT_EQ(fmt::format("{:12}", std::error_code(42, generic)), "generic:42 ");
|
||||||
std::error_code(42, fmt::system_category())));
|
EXPECT_EQ(fmt::format("{}", std::error_code(42, fmt::system_category())),
|
||||||
EXPECT_EQ("system:-42",
|
"system:42");
|
||||||
fmt::format(FMT_STRING("{0}"),
|
EXPECT_EQ(fmt::format("{}", std::error_code(-42, fmt::system_category())),
|
||||||
std::error_code(-42, fmt::system_category())));
|
"system:-42");
|
||||||
|
auto ec = std::make_error_code(std::errc::value_too_large);
|
||||||
|
EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
|
||||||
|
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
|
||||||
|
"\"generic:42\"");
|
||||||
|
EXPECT_EQ(fmt::format("{}",
|
||||||
|
std::map<std::error_code, int>{
|
||||||
|
{std::error_code(42, generic), 0}}),
|
||||||
|
"{\"generic:42\": 0}");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Catch> void exception_test() {
|
template <typename Catch> void exception_test() {
|
||||||
@ -362,11 +434,12 @@ TEST(std_test, format_atomic) {
|
|||||||
|
|
||||||
#ifdef __cpp_lib_atomic_flag_test
|
#ifdef __cpp_lib_atomic_flag_test
|
||||||
TEST(std_test, format_atomic_flag) {
|
TEST(std_test, format_atomic_flag) {
|
||||||
std::atomic_flag f = ATOMIC_FLAG_INIT;
|
std::atomic_flag f;
|
||||||
(void)f.test_and_set();
|
(void)f.test_and_set();
|
||||||
EXPECT_EQ(fmt::format("{}", f), "true");
|
EXPECT_EQ(fmt::format("{}", f), "true");
|
||||||
|
|
||||||
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
|
f.clear();
|
||||||
|
const std::atomic_flag& cf = f;
|
||||||
EXPECT_EQ(fmt::format("{}", cf), "false");
|
EXPECT_EQ(fmt::format("{}", cf), "false");
|
||||||
}
|
}
|
||||||
#endif // __cpp_lib_atomic_flag_test
|
#endif // __cpp_lib_atomic_flag_test
|
||||||
@ -388,3 +461,34 @@ TEST(std_test, format_shared_ptr) {
|
|||||||
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
|
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
|
||||||
fmt::format("{}", fmt::ptr(sp)));
|
fmt::format("{}", fmt::ptr(sp)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(std_test, format_reference_wrapper) {
|
||||||
|
int num = 35;
|
||||||
|
EXPECT_EQ(fmt::to_string(std::cref(num)), "35");
|
||||||
|
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
|
||||||
|
struct type_with_format_as {};
|
||||||
|
int format_as(type_with_format_as) { return 20; }
|
||||||
|
|
||||||
|
TEST(std_test, format_reference_wrapper_with_format_as) {
|
||||||
|
type_with_format_as t;
|
||||||
|
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
|
||||||
|
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct type_with_format_as_string {};
|
||||||
|
std::string format_as(type_with_format_as_string) { return "foo"; }
|
||||||
|
|
||||||
|
TEST(std_test, format_reference_wrapper_with_format_as_string) {
|
||||||
|
type_with_format_as_string t;
|
||||||
|
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
|
||||||
|
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
|
||||||
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
void throw_assertion_failure(const char* message);
|
void throw_assertion_failure(const char* message);
|
||||||
#define FMT_ASSERT(condition, message) \
|
#define FMT_ASSERT(condition, message) \
|
||||||
if (!(condition)) throw_assertion_failure(message);
|
((condition) ? (void)0 : throw_assertion_failure(message))
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
using testing::Contains;
|
using testing::Contains;
|
||||||
|
|
||||||
TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8()); }
|
TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8); }
|
||||||
|
|
||||||
TEST(unicode_test, legacy_locale) {
|
TEST(unicode_test, legacy_locale) {
|
||||||
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");
|
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");
|
||||||
|
|||||||
@ -36,12 +36,11 @@ std::locale do_get_locale(const char* name) {
|
|||||||
|
|
||||||
std::locale get_locale(const char* name, const char* alt_name) {
|
std::locale get_locale(const char* name, const char* alt_name) {
|
||||||
auto loc = do_get_locale(name);
|
auto loc = do_get_locale(name);
|
||||||
if (loc == std::locale::classic() && alt_name)
|
if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name);
|
||||||
loc = do_get_locale(alt_name);
|
|
||||||
#ifdef __OpenBSD__
|
#ifdef __OpenBSD__
|
||||||
// Locales are not working in OpenBSD:
|
// Locales are not working in OpenBSD:
|
||||||
// https://github.com/fmtlib/fmt/issues/3670.
|
// https://github.com/fmtlib/fmt/issues/3670.
|
||||||
loc = std::locale::classic();
|
loc = std::locale::classic();
|
||||||
#endif
|
#endif
|
||||||
if (loc == std::locale::classic())
|
if (loc == std::locale::classic())
|
||||||
fmt::print(stderr, "{} locale is missing.\n", name);
|
fmt::print(stderr, "{} locale is missing.\n", name);
|
||||||
|
|||||||
11
test/util.h
11
test/util.h
@ -31,17 +31,6 @@ extern const char* const file_content;
|
|||||||
// Opens a buffered file for reading.
|
// Opens a buffered file for reading.
|
||||||
auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file;
|
auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file;
|
||||||
|
|
||||||
inline auto safe_fopen(const char* filename, const char* mode) -> FILE* {
|
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
||||||
// Fix MSVC warning about "unsafe" fopen.
|
|
||||||
FILE* f = nullptr;
|
|
||||||
errno = fopen_s(&f, filename, mode);
|
|
||||||
return f;
|
|
||||||
#else
|
|
||||||
return std::fopen(filename, mode);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char> class basic_test_string {
|
template <typename Char> class basic_test_string {
|
||||||
private:
|
private:
|
||||||
std::basic_string<Char> value_;
|
std::basic_string<Char> value_;
|
||||||
|
|||||||
@ -72,17 +72,18 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(xchar_test, format) {
|
TEST(xchar_test, format) {
|
||||||
EXPECT_EQ(L"42", fmt::format(L"{}", 42));
|
EXPECT_EQ(fmt::format(L"{}", 42), L"42");
|
||||||
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
|
EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2");
|
||||||
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
|
EXPECT_EQ(fmt::format(L"{}", 1e100), L"1e+100");
|
||||||
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
|
EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc");
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", L'z'), L"z");
|
||||||
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
|
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
|
||||||
EXPECT_EQ(L"true", fmt::format(L"{}", true));
|
EXPECT_EQ(fmt::format(L"{}", true), L"true");
|
||||||
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
|
EXPECT_EQ(fmt::format(L"{0}", L'a'), L"a");
|
||||||
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
|
EXPECT_EQ(fmt::format(L"Letter {}", L'\x40e'), L"Letter \x40e"); // Ў
|
||||||
EXPECT_EQ(L"Cyrillic letter \x42e",
|
if (sizeof(wchar_t) == 4)
|
||||||
fmt::format(L"Cyrillic letter {}", L'\x42e'));
|
EXPECT_EQ(fmt::format(fmt::runtime(L"{:𓀨>3}"), 42), L"𓀨42");
|
||||||
EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
|
EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), L"abc1");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, is_formattable) {
|
TEST(xchar_test, is_formattable) {
|
||||||
@ -96,93 +97,6 @@ TEST(xchar_test, compile_time_string) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_CPLUSPLUS > 201103L
|
|
||||||
struct custom_char {
|
|
||||||
int value;
|
|
||||||
custom_char() = default;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
|
|
||||||
|
|
||||||
constexpr operator char() const {
|
|
||||||
return value <= 0xff ? static_cast<char>(value) : '\0';
|
|
||||||
}
|
|
||||||
constexpr bool operator<(custom_char c) const { return value < c.value; }
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
template <> struct char_traits<custom_char> {
|
|
||||||
using char_type = custom_char;
|
|
||||||
using int_type = int;
|
|
||||||
using off_type = streamoff;
|
|
||||||
using pos_type = streampos;
|
|
||||||
using state_type = mbstate_t;
|
|
||||||
|
|
||||||
static constexpr void assign(char_type& r, const char_type& a) { r = a; }
|
|
||||||
static constexpr bool eq(char_type a, char_type b) { return a == b; }
|
|
||||||
static constexpr bool lt(char_type a, char_type b) { return a < b; }
|
|
||||||
static FMT_CONSTEXPR int compare(const char_type* s1, const char_type* s2,
|
|
||||||
size_t count) {
|
|
||||||
for (; count; count--, s1++, s2++) {
|
|
||||||
if (lt(*s1, *s2)) return -1;
|
|
||||||
if (lt(*s2, *s1)) return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR size_t length(const char_type* s) {
|
|
||||||
size_t count = 0;
|
|
||||||
while (!eq(*s++, custom_char(0))) count++;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
static const char_type* find(const char_type*, size_t, const char_type&);
|
|
||||||
static FMT_CONSTEXPR char_type* move(char_type* dest, const char_type* src,
|
|
||||||
size_t count) {
|
|
||||||
if (count == 0) return dest;
|
|
||||||
char_type* ret = dest;
|
|
||||||
if (src < dest) {
|
|
||||||
dest += count;
|
|
||||||
src += count;
|
|
||||||
for (; count; count--) assign(*--dest, *--src);
|
|
||||||
} else if (src > dest)
|
|
||||||
copy(dest, src, count);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR char_type* copy(char_type* dest, const char_type* src,
|
|
||||||
size_t count) {
|
|
||||||
char_type* ret = dest;
|
|
||||||
for (; count; count--) assign(*dest++, *src++);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR char_type* assign(char_type* dest, std::size_t count,
|
|
||||||
char_type a) {
|
|
||||||
char_type* ret = dest;
|
|
||||||
for (; count; count--) assign(*dest++, a);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
static int_type not_eof(int_type);
|
|
||||||
static char_type to_char_type(int_type);
|
|
||||||
static int_type to_int_type(char_type);
|
|
||||||
static bool eq_int_type(int_type, int_type);
|
|
||||||
static int_type eof();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
auto to_ascii(custom_char c) -> char { return c; }
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
template <> struct is_char<custom_char> : std::true_type {};
|
|
||||||
FMT_END_NAMESPACE
|
|
||||||
|
|
||||||
TEST(xchar_test, format_custom_char) {
|
|
||||||
const custom_char format[] = {'{', '}', 0};
|
|
||||||
auto result = fmt::format(format, custom_char('x'));
|
|
||||||
EXPECT_EQ(result.size(), 1);
|
|
||||||
EXPECT_EQ(result[0], custom_char('x'));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(xchar_test, format_to) {
|
TEST(xchar_test, format_to) {
|
||||||
auto buf = std::vector<wchar_t>();
|
auto buf = std::vector<wchar_t>();
|
||||||
fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
|
fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
|
||||||
@ -232,7 +146,6 @@ TEST(format_test, wide_format_to_n) {
|
|||||||
EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
|
EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_USER_DEFINED_LITERALS
|
|
||||||
TEST(xchar_test, named_arg_udl) {
|
TEST(xchar_test, named_arg_udl) {
|
||||||
using namespace fmt::literals;
|
using namespace fmt::literals;
|
||||||
auto udl_a =
|
auto udl_a =
|
||||||
@ -243,7 +156,6 @@ TEST(xchar_test, named_arg_udl) {
|
|||||||
fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
|
fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
|
||||||
udl_a);
|
udl_a);
|
||||||
}
|
}
|
||||||
#endif // FMT_USE_USER_DEFINED_LITERALS
|
|
||||||
|
|
||||||
TEST(xchar_test, print) {
|
TEST(xchar_test, print) {
|
||||||
// Check that the wide print overload compiles.
|
// Check that the wide print overload compiles.
|
||||||
@ -260,6 +172,13 @@ TEST(xchar_test, join) {
|
|||||||
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
|
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_byte
|
||||||
|
TEST(xchar_test, join_bytes) {
|
||||||
|
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", fmt::join(v, L", ")), L"1, 2, 3");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
enum streamable_enum {};
|
enum streamable_enum {};
|
||||||
|
|
||||||
std::wostream& operator<<(std::wostream& os, streamable_enum) {
|
std::wostream& operator<<(std::wostream& os, streamable_enum) {
|
||||||
@ -313,106 +232,9 @@ TEST(xchar_test, chrono) {
|
|||||||
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
||||||
EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
|
EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
|
||||||
EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
|
EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
|
auto t = fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
|
||||||
std::locale* locptr = nullptr) {
|
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t), "1979-03-12 12:00:00");
|
||||||
auto loc = locptr ? *locptr : std::locale::classic();
|
|
||||||
auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
|
|
||||||
std::wostringstream os;
|
|
||||||
os.imbue(loc);
|
|
||||||
facet.put(os, os, L' ', timeptr, format.c_str(),
|
|
||||||
format.c_str() + format.size());
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Workaround a bug in older versions of Universal CRT.
|
|
||||||
auto str = os.str();
|
|
||||||
if (str == L"-0000") str = L"+0000";
|
|
||||||
return str;
|
|
||||||
#else
|
|
||||||
return os.str();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(chrono_test_wchar, time_point) {
|
|
||||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
|
||||||
std::chrono::system_clock::now());
|
|
||||||
|
|
||||||
std::vector<std::wstring> spec_list = {
|
|
||||||
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
|
|
||||||
L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U",
|
|
||||||
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
|
|
||||||
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
|
|
||||||
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
|
|
||||||
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
|
|
||||||
#ifndef _WIN32
|
|
||||||
// Disabled on Windows, because these formats is not consistent among
|
|
||||||
// platforms.
|
|
||||||
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
|
|
||||||
#elif !FMT_HAS_C99_STRFTIME
|
|
||||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
|
||||||
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
|
|
||||||
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
|
|
||||||
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
|
|
||||||
#endif
|
|
||||||
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
|
||||||
|
|
||||||
for (const auto& spec : spec_list) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::gmtime(&t);
|
|
||||||
|
|
||||||
auto sys_output = system_wcsftime(spec, &tm);
|
|
||||||
|
|
||||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timezone formatters tests makes sense for localtime.
|
|
||||||
#if FMT_HAS_C99_STRFTIME
|
|
||||||
spec_list = {L"%z", L"%Z"};
|
|
||||||
#else
|
|
||||||
spec_list = {L"%Z"};
|
|
||||||
#endif
|
|
||||||
for (const auto& spec : spec_list) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
|
|
||||||
auto sys_output = system_wcsftime(spec, &tm);
|
|
||||||
|
|
||||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
|
||||||
|
|
||||||
if (spec == L"%z") {
|
|
||||||
sys_output.insert(sys_output.end() - 2, 1, L':');
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
|
|
||||||
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
|
||||||
// the timezone in std::tm (if it presents on platform).
|
|
||||||
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::gmtime(&t);
|
|
||||||
|
|
||||||
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
|
|
||||||
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
|
|
||||||
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
|
||||||
auto tm = *std::gmtime(&t);
|
|
||||||
|
|
||||||
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
|
|
||||||
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
|
|
||||||
|
|
||||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
|
|
||||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
|
|
||||||
|
|
||||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
|
|
||||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, color) {
|
TEST(xchar_test, color) {
|
||||||
@ -557,7 +379,7 @@ TEST(locale_test, int_formatter) {
|
|||||||
f.parse(parse_ctx);
|
f.parse(parse_ctx);
|
||||||
auto buf = fmt::memory_buffer();
|
auto buf = fmt::memory_buffer();
|
||||||
fmt::basic_format_context<fmt::appender, char> format_ctx(
|
fmt::basic_format_context<fmt::appender, char> format_ctx(
|
||||||
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
|
fmt::appender(buf), {}, fmt::locale_ref(loc));
|
||||||
f.format(12345, format_ctx);
|
f.format(12345, format_ctx);
|
||||||
EXPECT_EQ(fmt::to_string(buf), "12,345");
|
EXPECT_EQ(fmt::to_string(buf), "12,345");
|
||||||
}
|
}
|
||||||
@ -568,11 +390,9 @@ TEST(locale_test, chrono_weekday) {
|
|||||||
auto sat = fmt::weekday(6);
|
auto sat = fmt::weekday(6);
|
||||||
EXPECT_EQ(fmt::format(L"{}", sat), L"Sat");
|
EXPECT_EQ(fmt::format(L"{}", sat), L"Sat");
|
||||||
if (loc != std::locale::classic()) {
|
if (loc != std::locale::classic()) {
|
||||||
// L'\xE1' is 'á'.
|
// L'\341' is 'á'.
|
||||||
auto saturdays = std::vector<std::wstring>{
|
auto saturdays =
|
||||||
L"s\xE1"
|
std::vector<std::wstring>{L"s\341b", L"s\341.", L"s\341b."};
|
||||||
"b",
|
|
||||||
L"s\xE1."};
|
|
||||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, L"{:L}", sat)));
|
EXPECT_THAT(saturdays, Contains(fmt::format(loc, L"{:L}", sat)));
|
||||||
}
|
}
|
||||||
std::locale::global(loc_old);
|
std::locale::global(loc_old);
|
||||||
@ -582,11 +402,20 @@ TEST(locale_test, sign) {
|
|||||||
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
|
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(std_test_xchar, format_bitset) {
|
||||||
|
auto bs = std::bitset<6>(42);
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", bs), L"101010");
|
||||||
|
EXPECT_EQ(fmt::format(L"{:0>8}", bs), L"00101010");
|
||||||
|
EXPECT_EQ(fmt::format(L"{:-^12}", bs), L"---101010---");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(std_test_xchar, complex) {
|
TEST(std_test_xchar, complex) {
|
||||||
auto s = fmt::format(L"{}", std::complex<double>(1, 2));
|
auto s = fmt::format(L"{}", std::complex<double>(1, 2));
|
||||||
EXPECT_EQ(s, L"(1+2i)");
|
EXPECT_EQ(s, L"(1+2i)");
|
||||||
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)), L"(1.00+2.00i)");
|
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)),
|
||||||
|
L"(1.00+2.00i)");
|
||||||
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) ");
|
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) ");
|
||||||
|
EXPECT_EQ(fmt::format(L"{:-<8}", std::complex<double>(1, 2)), L"(1+2i)--");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(std_test_xchar, optional) {
|
TEST(std_test_xchar, optional) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user