mirror of
https://github.com/fmtlib/fmt.git
synced 2025-06-25 09:21:41 +02:00
Compare commits
1147 Commits
Author | SHA1 | Date | |
---|---|---|---|
64dc8fbada | |||
fc8e3de7db | |||
57bee9fcdd | |||
dce52e491e | |||
9405a47245 | |||
495b8bf12e | |||
e221166fab | |||
035cab8da3 | |||
89c6ed12bf | |||
e462da828d | |||
79c66d66bd | |||
5d37f705f4 | |||
6bb370cec1 | |||
bb69201578 | |||
4fac7daaef | |||
3617c2795a | |||
9c0c1bcdbd | |||
187e8db1be | |||
c7f88180f6 | |||
8a2c3fb88f | |||
1164eda5af | |||
4482f6f1f0 | |||
796662a612 | |||
33ee4cc516 | |||
3bbf2c673c | |||
074c9c52ef | |||
3110ec5a23 | |||
3014b3d770 | |||
eab2ea9fc2 | |||
21ed92a6e9 | |||
04111dd1e4 | |||
817788fbf0 | |||
4511030af2 | |||
7812813a32 | |||
664cd6067d | |||
784e2a7b42 | |||
fc2a376d8e | |||
c5aafd8f90 | |||
eaddd1e3cd | |||
2d44577586 | |||
e46392ea2c | |||
c882790a2e | |||
121002d700 | |||
be51ee1ceb | |||
659de779e6 | |||
51b14b6c0c | |||
223a0fa55d | |||
ef72b471fc | |||
82246b8766 | |||
35f60377aa | |||
3a951a66cb | |||
e0136fc8bd | |||
ac1b5f3da5 | |||
fd62fba985 | |||
c652f8243a | |||
a9c7b9b8f7 | |||
e4f0564aa6 | |||
91533d3c33 | |||
0bbc9708f9 | |||
9d5b9defde | |||
215f21a038 | |||
c240d98ffd | |||
6ab73113fc | |||
713c7c7c62 | |||
9b1807a8a2 | |||
ec3b097cb9 | |||
c472a27818 | |||
201971e293 | |||
acad8cfab1 | |||
491ba2dda5 | |||
5abe9e8266 | |||
be3a3a5aed | |||
a3ab36c803 | |||
19cac63fe4 | |||
43419a4ada | |||
c089f7d497 | |||
aa5517f6b9 | |||
50140be7ae | |||
8b89454994 | |||
5380ff4d88 | |||
094b66e81d | |||
b69ae4854c | |||
0b843af56b | |||
12b1d8b14a | |||
e67f92c55c | |||
812733cc96 | |||
028f227752 | |||
5b0aa638cf | |||
6eaceb5f73 | |||
0697c5edb6 | |||
1031eedf27 | |||
90034e4c4b | |||
df40e94673 | |||
e6d5059cbb | |||
3b6e409cd8 | |||
249f03bbb7 | |||
7463c83205 | |||
1266c2b600 | |||
684e2fdc94 | |||
a1d586302f | |||
7a604cdd98 | |||
aeb54b0dd9 | |||
f88c020fc0 | |||
2eeddba756 | |||
2754546080 | |||
218cecb6d1 | |||
e9f4453b0e | |||
27c3674ce1 | |||
5dc3dd3d4a | |||
f8542cd988 | |||
4707373d33 | |||
79c00ad8f2 | |||
fbaaa5906b | |||
cde44ddb72 | |||
b04601b918 | |||
d3d30a46f0 | |||
7911d8d3f5 | |||
fbbfc3b03c | |||
509eac9575 | |||
85b38190d1 | |||
7aca36bca4 | |||
f5371a75f4 | |||
febdef43f5 | |||
f56756986b | |||
dcd282bb26 | |||
9c14474d30 | |||
1e96e01766 | |||
7e4bc94510 | |||
26c1ca4c3e | |||
1e865b3539 | |||
4a85db1ce1 | |||
0a985fd4c6 | |||
012cc709d0 | |||
d6590e3bd2 | |||
134aec40f0 | |||
48a476ae0f | |||
023c2018f7 | |||
800d4c8ac8 | |||
32865aeaab | |||
7b339795a1 | |||
ae9bbe1169 | |||
927dbd134d | |||
2a9a77dd8c | |||
1aee4bc90a | |||
e1bd6cc913 | |||
027fcaf05e | |||
716d69f27e | |||
ff7e73af66 | |||
2976e31ac9 | |||
807ee5ec31 | |||
d9a731d486 | |||
9c57357e05 | |||
2742611cad | |||
5092b198bc | |||
b4d9d82e1d | |||
d9fd695ac7 | |||
92614ecbf9 | |||
aaeca12d89 | |||
3d0c7ae385 | |||
04e3a79f76 | |||
e47e99bb09 | |||
9b6b0e403c | |||
4d1c6034eb | |||
a3348eccdd | |||
3a04481485 | |||
ad77331c04 | |||
d9ebc4e821 | |||
c00eb4f4c6 | |||
25af02f21a | |||
67cb2dad37 | |||
1aa98f8b93 | |||
a58c133821 | |||
aeee70a815 | |||
c771ba361c | |||
ab6e2272cc | |||
e4728409e7 | |||
e3ebf366a6 | |||
894faf3fed | |||
4eb97fa4e3 | |||
6b55c83252 | |||
2fe94ad7e3 | |||
3940de5952 | |||
c4d0f96a6d | |||
3b9c442689 | |||
08f98c7fac | |||
a151f955a0 | |||
42a225cbd9 | |||
bf20d19901 | |||
fc0884037e | |||
1aeed2dbca | |||
799bea4730 | |||
60cd5ea3f2 | |||
4fd9a00f35 | |||
5681563898 | |||
20931baf1d | |||
d58d19ba32 | |||
ee0659f8b6 | |||
8029bf955c | |||
2520f410c8 | |||
ee63f5f04e | |||
1aaf72fb6d | |||
c1313c2057 | |||
cb0f177c35 | |||
71677e5204 | |||
4db5723525 | |||
dc7f3ef2bb | |||
419ba86a91 | |||
6a5b4d5faf | |||
2599163b8a | |||
8ef22f7740 | |||
c0c4d1adab | |||
729a44e67d | |||
74c1118964 | |||
596508a928 | |||
043e3b3429 | |||
8b0cb944da | |||
117fc67077 | |||
c79a3841e8 | |||
5888de9f34 | |||
04b4b69b11 | |||
fd34a3d246 | |||
6d597e39c3 | |||
b9ce56d936 | |||
f889e52a15 | |||
34caecd6b6 | |||
a44c8f651b | |||
4b8bda25c0 | |||
6b5e6119ee | |||
7af1dc1d27 | |||
e77686f7a8 | |||
2207ea0b36 | |||
a212ff757f | |||
a76031e11d | |||
a7f280765c | |||
07d033ecb4 | |||
cdb4299acb | |||
7df2c82a8a | |||
6cf90d7cee | |||
2f1ad8ed3c | |||
371d8e2ee0 | |||
6397095ca4 | |||
f69a572538 | |||
11b07a56b2 | |||
b559cfd4c0 | |||
11d49491cb | |||
6ea6cf9464 | |||
9730a2af0a | |||
c2ed5f6863 | |||
7b66e72e2c | |||
d57b2a6525 | |||
bdfbd794e3 | |||
111de881fa | |||
d6e882ed84 | |||
f488eed101 | |||
652c3653bb | |||
fb19faa31b | |||
07211701f4 | |||
bba0a9d962 | |||
f1794a8853 | |||
0544a2279b | |||
5c222f0561 | |||
3def950b84 | |||
63fe2d5bd2 | |||
561834650a | |||
f20f50368f | |||
00235d8a99 | |||
2038bf6183 | |||
e41ac1f875 | |||
8465869d7b | |||
3d53d1539d | |||
20e4ef8b4c | |||
c4a3c2342a | |||
7a0d301753 | |||
f2b03facd9 | |||
02ad5e11da | |||
d141cdbeb0 | |||
cfc05e05f0 | |||
8ea312633b | |||
e461f3dbbe | |||
54014e42e3 | |||
3e7a29cc92 | |||
00a57a9f8f | |||
1d7384530e | |||
889bbf27a2 | |||
5f8473914c | |||
785908ee37 | |||
fbb70eec5c | |||
002bb759ff | |||
a3f762c5aa | |||
c3c27e5ab5 | |||
c6b1f181aa | |||
94564b058e | |||
0fc73a2a85 | |||
3156fcf5f4 | |||
f85fb9fdff | |||
0bc3d664e3 | |||
e5c46e13e8 | |||
49a3b58c8b | |||
d0c8d45a2e | |||
c9a10631cb | |||
3bd806f12f | |||
fd16bcb20c | |||
5221242f6e | |||
31a5f0d399 | |||
102a4d492a | |||
f68508b6ce | |||
9e8b86fd2d | |||
92fec0f050 | |||
4749cc930a | |||
78a0ba0a6a | |||
7a39837d96 | |||
55b6e92db5 | |||
69dc3a8535 | |||
27f4cdd586 | |||
70d61a0ae3 | |||
427b534054 | |||
e421d52713 | |||
a59678f376 | |||
c98254c3d7 | |||
c123a72844 | |||
3c8fad126c | |||
f28cf3302d | |||
55010a9d3a | |||
0193e7c428 | |||
3423d75475 | |||
f6b5cc9f84 | |||
59a298f124 | |||
36c2948225 | |||
c9fe1fa5ba | |||
dccddc2bdb | |||
0e36681b8e | |||
1de80f5b22 | |||
2039dce75f | |||
d551b88a6d | |||
16c3514d01 | |||
206000a017 | |||
76ee490468 | |||
e77b22d6da | |||
07039f4b19 | |||
4678192c88 | |||
7c3d3dfa29 | |||
ef826b86cb | |||
5223f552c8 | |||
cfde93afe0 | |||
986a5a6c2c | |||
7c8b35ff32 | |||
3eeb084e71 | |||
2ac0bfe59e | |||
024741b476 | |||
f4c95f6dd9 | |||
d4fbeacc33 | |||
0eef389ddb | |||
e27b1ce50c | |||
9f8b6daca2 | |||
6060bcfc8a | |||
ff9673463c | |||
1085cc2178 | |||
11addaa16e | |||
760ca5ccc0 | |||
290d3f8b61 | |||
aa09e0f5dd | |||
d142579e97 | |||
f286139d22 | |||
7b9d69b827 | |||
cbd861f188 | |||
faf972f039 | |||
622d1c0423 | |||
634c948769 | |||
a04e3a2dc8 | |||
87876d5474 | |||
d338d66324 | |||
272660e704 | |||
5a95c5ae2c | |||
70e67ae018 | |||
ad97258915 | |||
ed2a6377e7 | |||
9976869549 | |||
8c1b22ba6d | |||
2dba1cfac1 | |||
d7ba6c3ea8 | |||
bf9904ee4d | |||
577bce9029 | |||
ba4c7f193b | |||
e9e89b355b | |||
9bb406d78d | |||
11a14db286 | |||
832ec098fc | |||
486a80e8ef | |||
19d45f4b31 | |||
5a2b88f6e9 | |||
00a39ad5f8 | |||
ff37e41625 | |||
0901176fe4 | |||
a9a9018191 | |||
4a7801c3ec | |||
517578f80e | |||
85442ed045 | |||
6a12b13a85 | |||
1cfe3c7382 | |||
c06014792b | |||
6fe04871f4 | |||
9d67988aed | |||
765b451edd | |||
17c993c753 | |||
dde6937319 | |||
272b0f36b3 | |||
126c8cb46b | |||
98b9ff47a1 | |||
ece4b4b33a | |||
a70a4ae053 | |||
7612f18dc8 | |||
b9f2c27661 | |||
4e21baff43 | |||
683ef11ab9 | |||
ca466374bd | |||
5a2a185682 | |||
ee52a6dc40 | |||
82607efb57 | |||
35a2c2a743 | |||
b955e7a6b2 | |||
883d9595c5 | |||
1f308a3cea | |||
1cd9899cf3 | |||
069131dc25 | |||
dd8f38fcbb | |||
a216f2562d | |||
0c0926395d | |||
bc13c6de39 | |||
8ec0b9e33b | |||
b99c2bd345 | |||
c04a24399a | |||
b099a56f9f | |||
703005c8ba | |||
51f0178625 | |||
5d59dcf66e | |||
c242dd402c | |||
2216e0b779 | |||
1c83a49be9 | |||
2617384d8e | |||
34b8acaef7 | |||
6326c18906 | |||
5c4b0c86fb | |||
00149c0b6a | |||
c5c968cb22 | |||
128cbdeb2f | |||
18af1dc460 | |||
d1e6f0f8c6 | |||
5a0d99fa0b | |||
6e2e6b796f | |||
24b677d053 | |||
63271a51c4 | |||
61b4c923d7 | |||
2a2e4c5801 | |||
be48f4d657 | |||
13e652939b | |||
71fb113818 | |||
08d22503bb | |||
56f518a98f | |||
b7f2933744 | |||
7483dfc652 | |||
95c358f721 | |||
39c3c4ec22 | |||
e9c1c415b8 | |||
21d93bfd33 | |||
9a92eb4158 | |||
0dd91e20d5 | |||
ce14eafc24 | |||
8d70c0edab | |||
813ac49543 | |||
4ab01fb198 | |||
d5036b11b1 | |||
2581946231 | |||
b35db4e006 | |||
d35f1ad5c1 | |||
8f1902c05a | |||
6469b9037c | |||
7d4c92fb00 | |||
0763d8cadf | |||
5466373a11 | |||
588bdb5404 | |||
54f22a3eef | |||
ea94d6d93c | |||
57280762b6 | |||
ced3037523 | |||
dd2bc998ab | |||
08da1adcf6 | |||
3be0cc2087 | |||
9648bdce30 | |||
d1aebdbde0 | |||
8f0fadfaaa | |||
02896dabee | |||
0036a1d195 | |||
2a9b314627 | |||
2165bef4ca | |||
4862930845 | |||
3207a8bbbf | |||
6214f15a0c | |||
cd2c78fb8a | |||
4211d86539 | |||
39f28424ca | |||
84feeb0f36 | |||
2665afb515 | |||
d0abe7c246 | |||
50fb0b5eae | |||
16f2ef91ab | |||
4b885c8633 | |||
5238055f40 | |||
9ac088f376 | |||
849c9f6168 | |||
23892caf53 | |||
8e6390c32c | |||
51a33713fc | |||
9c3af11a92 | |||
9d7b53cb9b | |||
f0095ccd34 | |||
4f0eadfce4 | |||
400b953fbb | |||
38bcc04a11 | |||
c738c3431f | |||
ed7c4320f6 | |||
9155e2de4c | |||
38127d9ec0 | |||
c9c0e5077d | |||
ccf4ccde23 | |||
e96a92f869 | |||
fd43e4dcbc | |||
3d51ccdaae | |||
833377ff1e | |||
53ca0cbe75 | |||
342973b349 | |||
355be4b13f | |||
0cd0fb9184 | |||
d1a6e5603f | |||
84a36b99bf | |||
ab7c33ede0 | |||
77258f6069 | |||
d23e315ea2 | |||
f085c3d7a0 | |||
69bdc20a3c | |||
847aac4315 | |||
39818e7979 | |||
0e6f989b0d | |||
1678ed6235 | |||
ca821982ee | |||
ce6e7d8620 | |||
fc56af14c2 | |||
bb006f9735 | |||
6956b10b2d | |||
b4f9a05894 | |||
8f9ddf452d | |||
dacd1356e4 | |||
d3c523e0d2 | |||
2c25df089f | |||
553022dc56 | |||
8a040d187a | |||
064cac2bf9 | |||
5b2c740ad8 | |||
b9ab5c8836 | |||
c47f211296 | |||
54d3b1710e | |||
128f007b25 | |||
841aad95b4 | |||
1d4199f46b | |||
c5d4fcb119 | |||
6271406233 | |||
52bd62c72f | |||
f4bbc54cc4 | |||
d8910af80d | |||
9260114162 | |||
24c9751558 | |||
a1c6bfd77b | |||
42eccac454 | |||
aec504344a | |||
0b41145443 | |||
00f3d16b12 | |||
99c2f7a349 | |||
b441532396 | |||
1dbadb6527 | |||
09dbad47e1 | |||
e2facffe4d | |||
273d8865e3 | |||
5a8bf1f6a3 | |||
78776ee4e2 | |||
266107f57c | |||
2e0d64cf2f | |||
95da484727 | |||
06b3a1000c | |||
dac42f52b2 | |||
7c43f8b896 | |||
c62e4c30f4 | |||
0d6b70d96b | |||
15c10b0c66 | |||
308510eb4f | |||
afe23e7f10 | |||
b49af043d7 | |||
14848875bf | |||
7d8c34018e | |||
b966afcc7a | |||
ec5315a987 | |||
4f8778bab9 | |||
e2d87548f8 | |||
f7151d384b | |||
0fb8ef8f79 | |||
1b23e25f95 | |||
35c71ff536 | |||
243d8bebd1 | |||
9b34681d97 | |||
4dc7170d21 | |||
9cb347b4b2 | |||
0f85a4683a | |||
417e1cee9e | |||
f7e900e12e | |||
d9661c8f3b | |||
14a2a64df4 | |||
6ae402fd0b | |||
a6408a3b09 | |||
1147782c79 | |||
2f3f3862fa | |||
d0bded5988 | |||
8308f52c2a | |||
6151d0dc1e | |||
5a1127b726 | |||
b8ff3c1820 | |||
c8d8b88223 | |||
d28101878a | |||
bac14ef985 | |||
8f9db3fcb8 | |||
af567538a0 | |||
cdf877d4b1 | |||
eef4ba9c02 | |||
a1ea8a82c3 | |||
a457e16360 | |||
05bc87a66f | |||
605b603735 | |||
85ba271639 | |||
d9835737f0 | |||
f9e0e90441 | |||
60f5d24411 | |||
30e1302e73 | |||
87c5cd46ac | |||
6a9016ea60 | |||
6e1fc01752 | |||
e718ec3e93 | |||
772aeca338 | |||
684b5b0e40 | |||
d8b9254301 | |||
835b910e7d | |||
578874033a | |||
640acba850 | |||
d8e1c9f175 | |||
2797588be1 | |||
e8eff3b8fd | |||
ab0f7d7fdc | |||
29cc8282b1 | |||
3f69af3aaf | |||
499047e132 | |||
78c67157c1 | |||
b31bc2dc9f | |||
95e1aa2dc5 | |||
7e72673d87 | |||
13b117b5bc | |||
ee0fed639c | |||
c5979d564e | |||
e6ef927e6b | |||
58aa04573f | |||
1980ca8c4e | |||
2a25e2bf4d | |||
b0b56b4379 | |||
373262f9fb | |||
ce519e939b | |||
acef0bb51a | |||
8bf28e6bb1 | |||
9c418bc468 | |||
456efa4666 | |||
80dc7cceb8 | |||
7fd535c6ae | |||
b4b8917caf | |||
e4f2cf455e | |||
6972b5f3d2 | |||
ac35208115 | |||
532e846b86 | |||
f8c2f8480a | |||
0fe0b15e71 | |||
061e364b25 | |||
018688da2a | |||
9ec5592bb5 | |||
cdc5ef6710 | |||
c9dd1eb97d | |||
d09b5c1453 | |||
bbd6ed5bc5 | |||
a750bf3ac6 | |||
1256541d7a | |||
4fa4c9248f | |||
aa89e380d9 | |||
5a37e182de | |||
fa43fd1444 | |||
3551f5d118 | |||
e737672614 | |||
25a41b80fc | |||
9293f7072e | |||
c20874c28f | |||
5de0bc1d4f | |||
a6fafe2f01 | |||
33f9a6d360 | |||
aabe0a8473 | |||
1f4a76d2c8 | |||
4a6eadbde0 | |||
683a74501f | |||
f43416e1d7 | |||
5a493560f5 | |||
9ed0a98178 | |||
dac753b81e | |||
119f7dc3d6 | |||
22a68d1613 | |||
d0110b7e35 | |||
3f4839ce3d | |||
7bdf0628b1 | |||
fc1355114d | |||
926233bde8 | |||
0683fa7d1d | |||
6ce207b9a5 | |||
07b1c1a15f | |||
58992761cf | |||
b8957f50c3 | |||
df66516ed3 | |||
a57baa69a5 | |||
85534a1397 | |||
a2fa5d6288 | |||
cd3003683d | |||
d1ef29d679 | |||
5f41bb0f77 | |||
a58a6b27c3 | |||
a036cc97b7 | |||
38c7def47a | |||
5533641319 | |||
55dfdd9299 | |||
2c734c9bca | |||
6cdd1be93e | |||
bcc20b29df | |||
befd7d4a2f | |||
f8640d4050 | |||
f81c14aa1e | |||
5555651ce0 | |||
b268f8815d | |||
aa9b09a9e3 | |||
986fa00406 | |||
7abc3c01e0 | |||
6d14f78115 | |||
9534b9fe69 | |||
60dc273513 | |||
b5dac0f0f8 | |||
a07627b1f8 | |||
1b8f499ee1 | |||
f428d286a1 | |||
beb248b6ac | |||
1936dddc3c | |||
14f6bd0f4e | |||
e01d26e1a4 | |||
e528d919a8 | |||
4881677268 | |||
3302fd1088 | |||
4c2d637203 | |||
beaff39618 | |||
ffa0a0834a | |||
038057eb3e | |||
5bedcb665b | |||
2435ea4113 | |||
8c6215f5de | |||
10ebe6cb48 | |||
1ac50fcb5a | |||
e098be8e88 | |||
8cf0afaf1c | |||
e29f93e8a8 | |||
4e8d000f76 | |||
7787792e8d | |||
6ee5e507c7 | |||
06ee32d1b5 | |||
86bb7fe614 | |||
959a9f5cad | |||
4f7df299ea | |||
b3ab0bc7e3 | |||
701ed6c874 | |||
8f2131cf2d | |||
32c4af8f0d | |||
295a60ec8d | |||
a4fae96c96 | |||
263bb0e68d | |||
0506b328b5 | |||
4e426c19d0 | |||
9795d87348 | |||
2eb0be0b73 | |||
cd95579834 | |||
98639d0f6f | |||
ab5e0632fe | |||
b123129f4e | |||
81d2b986af | |||
7a0b1d5781 | |||
9f0617cbfb | |||
75b07598fe | |||
dfbb6975b3 | |||
5b3052f999 | |||
506ff320f2 | |||
a30b279bad | |||
6a2495c840 | |||
cba5970cd8 | |||
689081d832 | |||
cc09f1a679 | |||
e4eb242ce8 | |||
ce98e0c6a0 | |||
49544ea943 | |||
6b7bfed40c | |||
bcab36da3f | |||
1689e73e90 | |||
0103408a5c | |||
38a16ecba2 | |||
205eb3a8f2 | |||
fe61b8c630 | |||
867b15d77c | |||
98cb9f9931 | |||
95077d60c9 | |||
bc49f094e7 | |||
cef6dfb422 | |||
c8703ba40b | |||
ab4405bea5 | |||
78a55e2898 | |||
d0a2494a99 | |||
89d009ba6e | |||
1f4ff47b41 | |||
eb52ac7a35 | |||
e904e891bd | |||
771292c328 | |||
86bf6045c6 | |||
5f7f7b954d | |||
5d3f0741e3 | |||
563cbb6c21 | |||
425778aa67 | |||
69a84198b0 | |||
5c04504932 | |||
556a1cfb34 | |||
28a8eae850 | |||
236fea1f00 | |||
e50ced88c6 | |||
112755cf91 | |||
4081b2fe94 | |||
2d9311e860 | |||
b3a4f28ad1 | |||
97c8873214 | |||
bb68f6089b | |||
f4ca065cfb | |||
cb224ecaa3 | |||
7977c2b4d0 | |||
e54eb67639 | |||
4fe0b11195 | |||
df4bd60f42 | |||
764fb35e1f | |||
e1bdc0ecaf | |||
39bde329bd | |||
204d299abb | |||
e0995b1c14 | |||
4af178bdfe | |||
aa41dc02b1 | |||
6a77ea3c93 | |||
62c72059d9 | |||
c10e3f7f4f | |||
e542e6953e | |||
530cf316b8 | |||
740385d636 | |||
cd4651116e | |||
46291be348 | |||
90071c1df0 | |||
25293d7ac6 | |||
5024742f8a | |||
0452a4e71f | |||
8de96817ce | |||
47e167679a | |||
f0a42346a4 | |||
86287b8d56 | |||
8924211f3b | |||
525e7649cf | |||
0ecb3d1829 | |||
9755307842 | |||
7446818f98 | |||
280b5612c0 | |||
e57ec7d563 | |||
2a3f4de3f4 | |||
27fdb4ead2 | |||
297e0bad8c | |||
e3b4c22ec9 | |||
da8278e1e3 | |||
17fba753c4 | |||
71e705a273 | |||
74654c8cbb | |||
f468b203ad | |||
20d4f2e836 | |||
08370c39ff | |||
bd3c792507 | |||
8d3fd86d6d | |||
4034715713 | |||
37d738fa6b | |||
271eff149f | |||
010efc310f | |||
811c8b58c5 | |||
82c4e2236a | |||
63e40c9614 | |||
2f448ed565 | |||
af28305961 | |||
48ea8193df | |||
1d112bdd1e | |||
5eb292a653 | |||
7e56b6b6cb | |||
41d97e1ef4 | |||
01c37e0a4b | |||
a5e7e7db95 | |||
bf19051a9f | |||
3c13a88b14 | |||
f6d75c534c | |||
e9c0b2d69e | |||
7eddbfed53 | |||
b347b3023f | |||
3541880efd | |||
7b50dc0b24 | |||
2805243103 | |||
34f22e88a1 | |||
a18b3fbbdc | |||
7277035736 | |||
7612c1ea87 | |||
b91d39f20b | |||
b4b64b9cce | |||
712abe40f2 | |||
af8a180aed | |||
a581e9e5d8 | |||
05a28312cf | |||
4d0aa4d8fe | |||
575f401896 | |||
6f3536f974 | |||
90ef46df0b | |||
3ae88147e2 | |||
6417952574 | |||
79694d424c | |||
51f2e2ca27 | |||
68555fdbd2 | |||
63e0c35412 | |||
2213a7110c | |||
79ba37f3bf | |||
a905d8f704 | |||
762c33a964 | |||
253d63159f | |||
c156093ffd | |||
34179b3354 | |||
0651e4598b | |||
6c025520aa | |||
51f8d0cc21 | |||
1305cbeb6f | |||
2d4fde3a2e | |||
5fd89d50e4 | |||
605ce5e429 | |||
085171e7e6 | |||
aa729bf25b | |||
aa2ddf9b86 | |||
c1654ce487 | |||
33712dc07a | |||
e5942ac9dd | |||
aae7a1338c | |||
6bcde9aab2 | |||
bb0db5e51e | |||
16410056bf | |||
2591ab91c3 | |||
d5b8002dcb | |||
821471e1d1 | |||
2e620ddbcd | |||
2f7e08856b | |||
c46a8de4e1 | |||
2696dc9273 | |||
0016da7ab3 | |||
ce3f76994a | |||
3b6248f602 | |||
2d9b1dd0ad | |||
1f0600a23b | |||
2ecdbb986d | |||
6f81ea151a | |||
0c8ffe9b0f | |||
42699bf408 | |||
bc51a8df04 | |||
45da432d60 | |||
d55e61f120 | |||
7e6827521a | |||
1d696dc280 | |||
f674434a67 | |||
5b5a597198 | |||
f80ed64dd9 | |||
3813966497 | |||
dce8e49b4f | |||
78b5944313 | |||
f233b56cdd | |||
595902f8a0 | |||
4f2ee8921d | |||
58a044be5d | |||
efe3694f15 | |||
9f312fe87e | |||
fb289cf56b | |||
86f0a7046e | |||
bff4d18efb | |||
f19b8885f2 | |||
f8e00a084a | |||
6cccdc24bc | |||
69902c1787 | |||
1edd38b96e | |||
e66ba16923 | |||
f39e6fb617 | |||
77b627be20 | |||
5dff01d31b | |||
d16d585e64 | |||
c7e6d8afb0 | |||
92bff2fe2c | |||
a0dcfbc57b | |||
1651b2d433 | |||
06895a7687 | |||
92a448a071 | |||
6be6544668 | |||
951e0d2333 | |||
f9f02df719 | |||
76e97dc4df | |||
e204df0e66 | |||
1c8bb54703 | |||
4b69c78751 | |||
fb0aeb8209 | |||
54daa0864a | |||
6fb7c6fb25 | |||
16985fdadf | |||
1378ddaefd | |||
4fd95e4b4d | |||
e06ae32294 | |||
7fc3d1f54c | |||
065889a593 | |||
d0dd678693 | |||
0e7cef069b | |||
e2c8c4557a | |||
e4c954ff0e | |||
c13f79e09e | |||
d7921d649a | |||
cd4af11efc | |||
1ebc2f7cc6 | |||
f4c997062a | |||
72920ba30a | |||
0907c08ae5 | |||
37c8f4eaf3 | |||
eaaaec9992 | |||
ccf8561cb3 | |||
0cc73ebf79 | |||
33efc3c94f | |||
4a4fc225ed | |||
61602a75db | |||
2f8fc29e9b | |||
717b226b59 | |||
2a69f56769 | |||
ea76933802 | |||
5413713c95 | |||
57f462428d | |||
0b6e7cc60a | |||
e587adb4e9 | |||
279d698e1b | |||
76cfb50b2d | |||
208291205d | |||
8d9ab96736 | |||
734344931f | |||
2a47a1e48f | |||
7c4c5c79d2 | |||
f0b84da5ff | |||
a3dfd6f927 | |||
51d05521e9 | |||
21c8b5c142 | |||
d82fdcc9e2 | |||
633213d96f | |||
b9d749095e | |||
86b63bb71a | |||
cbf6be9604 | |||
229ee9b469 | |||
2b7a146fa1 | |||
89d0c7124b | |||
e8f2580a43 | |||
6cefe55ac7 | |||
64e2da15cd | |||
1c8c810f88 | |||
c2399ccfca | |||
a7c5db06d5 | |||
a4c22acd0a | |||
0c1f4b5a0d | |||
63b422ee5e | |||
26e81a6731 | |||
de5fc6af3b | |||
9c2edfd1aa | |||
810357c014 | |||
0a7032a400 | |||
95d3abf95c | |||
98626093d2 | |||
47f8d7a345 | |||
46a63b7087 | |||
430f393d6f | |||
febffa4e64 | |||
d69e2da221 | |||
ce73ea37fb | |||
d39d661b18 | |||
c228bfe882 | |||
38ce19f738 | |||
d11849bc0b | |||
c08518a25b | |||
e2837084ee | |||
9f0c003371 | |||
d615137ca0 | |||
26b47b6fb5 | |||
7a01c9c523 | |||
b17d5c4f5d | |||
eb90da2e82 | |||
9d3cd0afb2 | |||
18024853b6 | |||
f5d4215b7c | |||
c26349f4d2 | |||
f4b11ef6e2 | |||
0097cf113d | |||
8fa20b471b | |||
a03bd3ddb0 | |||
c108ee1d59 | |||
a8074a865a | |||
5f62954864 | |||
bd903f96ac | |||
16cac46a09 | |||
415cd51913 | |||
e1bfb59619 | |||
ba8d98cbb7 | |||
04a1f6e991 | |||
e4f57bfd7f | |||
d870468159 | |||
e8ec09ae83 | |||
a2c4fed981 | |||
36406509d9 | |||
60c43e8703 | |||
b998e0f30b | |||
c5adfc51c5 | |||
c4ad94ce26 | |||
c1429651eb | |||
638db5ca5e | |||
c090569751 | |||
1efdb2dde4 | |||
dc69afad14 | |||
445f5d392b | |||
23063c3444 | |||
f57b62575c |
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@ -1,6 +1,6 @@
|
||||
<!-- Please read the contribution guidelines before submitting a pull request. -->
|
||||
<!-- By submitting this pull request, you agree that your contributions are licensed under the {fmt} license,
|
||||
and agree to future changes to the licensing. -->
|
||||
<!-- If you're a first-time contributor, please acknowledge it by leaving the statement below. -->
|
||||
|
||||
I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing.
|
||||
<!--
|
||||
Please read the contribution guidelines before submitting a pull request:
|
||||
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
|
||||
By submitting this pull request, you agree that your contributions are licensed
|
||||
under the {fmt} license, and agree to future changes to the licensing.
|
||||
-->
|
||||
|
24
.github/workflows/doc.yml
vendored
Normal file
24
.github/workflows/doc.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: doc
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Build Environment
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install doxygen python3-virtualenv
|
||||
sudo npm install -g less clean-css
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
env:
|
||||
KEY: ${{secrets.KEY}}
|
||||
run: $GITHUB_WORKSPACE/support/build-docs.py
|
79
.github/workflows/linux.yml
vendored
Normal file
79
.github/workflows/linux.yml
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
name: linux
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
cxx: [g++-4.8, g++-10, clang++-9]
|
||||
build_type: [Debug, Release]
|
||||
std: [11]
|
||||
os: [ubuntu-18.04]
|
||||
include:
|
||||
- cxx: g++-4.8
|
||||
install: sudo apt install g++-4.8
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-8
|
||||
build_type: Debug
|
||||
std: 14
|
||||
install: sudo apt install g++-8
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-10
|
||||
build_type: Debug
|
||||
std: 17
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
install: sudo apt install g++-11
|
||||
- cxx: clang++-9
|
||||
build_type: Debug
|
||||
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||
std: 17
|
||||
os: ubuntu-18.04
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
cxxflags: -stdlib=libc++
|
||||
os: ubuntu-20.04
|
||||
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
||||
- shared: -DBUILD_SHARED_LIBS=ON
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Build Environment
|
||||
run: |
|
||||
${{matrix.install}}
|
||||
sudo apt install locales-all
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
env:
|
||||
CXX: ${{matrix.cxx}}
|
||||
CXXFLAGS: ${{matrix.cxxflags}}
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`nproc`
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: ctest -C ${{matrix.build_type}}
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
37
.github/workflows/macos.yml
vendored
Normal file
37
.github/workflows/macos.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: macos
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
include:
|
||||
- shared: -DBUILD_SHARED_LIBS=ON
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`sysctl -n hw.logicalcpu`
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: ctest -C ${{matrix.build_type}}
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
60
.github/workflows/windows.yml
vendored
Normal file
60
.github/workflows/windows.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
name: windows
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
|
||||
# respectively: https://github.com/actions/virtual-environments.
|
||||
os: [windows-2016, windows-2019]
|
||||
platform: [Win32, x64]
|
||||
build_type: [Debug, Release]
|
||||
standard: [11, 17, 20]
|
||||
include:
|
||||
- os: windows-2016
|
||||
platform: Win32
|
||||
build_type: Debug
|
||||
shared: -DBUILD_SHARED_LIBS=ON
|
||||
exclude:
|
||||
- os: windows-2016
|
||||
platform: Win32
|
||||
- os: windows-2016
|
||||
standard: 17
|
||||
- os: windows-2016
|
||||
standard: 20
|
||||
- os: windows-2019
|
||||
standard: 11
|
||||
- os: windows-2019
|
||||
standard: 20
|
||||
platform: Win32
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
# Use a bash shell for $GITHUB_WORKSPACE.
|
||||
shell: bash
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||
-A ${{matrix.platform}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
|
||||
$GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: ctest -C ${{matrix.build_type}} -V
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ gradle/
|
||||
gradlew*
|
||||
local.properties
|
||||
build/
|
||||
support/.cxx
|
||||
|
||||
bin/
|
||||
/_CPack_Packages
|
||||
|
101
.travis.yml
101
.travis.yml
@ -1,101 +0,0 @@
|
||||
language: cpp
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
os: linux
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: |-
|
||||
a1eovNn4uol9won7ghr67eD3/59oeESN+G9bWE+ecI1V6yRseG9whniGhIpC/YfMW/Qz5I
|
||||
5sxSmFjaw9bxCISNwUIrL1O5x2AmRYTnFcXk4dFsUvlZg+WeF/aKyBYCNRM8C2ndbBmtAO
|
||||
o1F2EwFbiso0EmtzhAPs19ujiVxkLn4=
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Documentation
|
||||
- env: BUILD=Doc
|
||||
sudo: required
|
||||
# g++ 6 on Linux with C++14
|
||||
- env: COMPILER=g++-6 BUILD=Debug STANDARD=14
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-6
|
||||
- env: COMPILER=g++-6 BUILD=Release STANDARD=14
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-6
|
||||
# g++ 8 on Linux with C++17
|
||||
- env: COMPILER=g++-8 BUILD=Debug STANDARD=17
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-8
|
||||
- env: COMPILER=g++-8 BUILD=Release STANDARD=17
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-8
|
||||
|
||||
# Apple clang on OS X with C++14
|
||||
- env: BUILD=Debug STANDARD=14
|
||||
compiler: clang
|
||||
os: osx
|
||||
- env: BUILD=Release STANDARD=14
|
||||
compiler: clang
|
||||
os: osx
|
||||
# clang 6.0 on Linux with C++14 (builds the fuzzers as well)
|
||||
- env: COMPILER=clang++-6.0 BUILD=Debug STANDARD=14 ENABLE_FUZZING=1
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- clang-6.0
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty
|
||||
- llvm-toolchain-trusty-6.0
|
||||
# clang 4.0 on Linux with C++14
|
||||
- env: COMPILER=clang++-4.0 BUILD=Debug STANDARD=11
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- clang-4.0
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty
|
||||
- llvm-toolchain-trusty-4.0
|
||||
# g++ 4.8 on Linux with C++11
|
||||
- env: COMPILER=g++-4.8 BUILD=Debug STANDARD=11
|
||||
compiler: gcc
|
||||
|
||||
before_script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then export CXX=${COMPILER}; fi
|
||||
- if [[ "${BUILD}" != "Doc" ]]; then ${CXX} --version; fi
|
||||
|
||||
script:
|
||||
- support/travis-build.py
|
170
CMakeLists.txt
170
CMakeLists.txt
@ -1,44 +1,63 @@
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
|
||||
# Use newer policies if available, up to most recent tested version of CMake.
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.11)
|
||||
# Fallback for using newer policies on CMake <3.12.
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
else()
|
||||
cmake_policy(VERSION 3.11)
|
||||
endif()
|
||||
|
||||
# Determine if fmt is built as a subproject (using add_subdirectory)
|
||||
# or if it is the master project.
|
||||
set(MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(MASTER_PROJECT ON)
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
if (NOT DEFINED FMT_MASTER_PROJECT)
|
||||
set(FMT_MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(FMT_MASTER_PROJECT ON)
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Joins arguments and places the results in ${result_var}.
|
||||
function(join result_var)
|
||||
set(result )
|
||||
set(result "")
|
||||
foreach (arg ${ARGN})
|
||||
set(result "${result}${arg}")
|
||||
endforeach ()
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(enable_module target)
|
||||
if (MSVC)
|
||||
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||
target_compile_options(${target}
|
||||
PRIVATE /interface /ifcOutput ${BMI}
|
||||
INTERFACE /reference fmt=${BMI})
|
||||
endif ()
|
||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||
endfunction()
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
# Sets a cache variable with a docstring joined from multiple arguments:
|
||||
# set(<variable> <value>... CACHE <type> <docstring>...)
|
||||
# This allows splitting a long docstring for readability.
|
||||
function(set_verbose)
|
||||
cmake_parse_arguments(SET_VERBOSE "" "" "CACHE" ${ARGN})
|
||||
list(GET SET_VERBOSE_CACHE 0 type)
|
||||
list(REMOVE_AT SET_VERBOSE_CACHE 0)
|
||||
join(doc ${SET_VERBOSE_CACHE})
|
||||
set(${SET_VERBOSE_UNPARSED_ARGUMENTS} CACHE ${type} ${doc})
|
||||
# cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use
|
||||
# list instead.
|
||||
list(GET ARGN 0 var)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
list(GET ARGN 0 val)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
list(GET ARGN 0 type)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
join(doc ${ARGN})
|
||||
set(${var} ${val} CACHE ${type} ${doc})
|
||||
endfunction()
|
||||
|
||||
# Set the default CMAKE_BUILD_TYPE to Release.
|
||||
# This should be done before the project command since the latter can set
|
||||
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
||||
if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||
if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
|
||||
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
@ -47,20 +66,41 @@ endif ()
|
||||
project(FMT CXX)
|
||||
include(GNUInstallDirs)
|
||||
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
|
||||
"Installation directory for include files, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
"Installation directory for include files, a relative path that "
|
||||
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||
|
||||
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||
OFF)
|
||||
|
||||
# Options that control generation of various targets.
|
||||
option(FMT_DOC "Generate the doc target." ${MASTER_PROJECT})
|
||||
option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
|
||||
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT})
|
||||
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
||||
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
||||
# msvc 16.10-pre4
|
||||
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
endif ()
|
||||
if (NOT FMT_CAN_MODULE)
|
||||
set(FMT_MODULE OFF)
|
||||
message(STATUS "Module support is disabled.")
|
||||
endif ()
|
||||
if (FMT_TEST AND FMT_MODULE)
|
||||
# The tests require {fmt} to be compiled as traditional library
|
||||
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||
endif ()
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
|
||||
if (FMT_SYSTEM_HEADERS)
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
||||
endif ()
|
||||
|
||||
# Get version from core.h
|
||||
file(READ include/fmt/core.h core_h)
|
||||
@ -96,24 +136,36 @@ if (${index} GREATER -1)
|
||||
endif ()
|
||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||
|
||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||
"Preset for the export of private symbols")
|
||||
set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS
|
||||
hidden default)
|
||||
endif ()
|
||||
|
||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN)
|
||||
set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL
|
||||
"Whether to add a compile flag to hide symbols of inline functions")
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
||||
-Wold-style-cast -Wundef
|
||||
-Wredundant-decls -Wwrite-strings -Wpointer-arith
|
||||
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
|
||||
-Wcast-align -Wnon-virtual-dtor
|
||||
-Wcast-align
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Woverloaded-virtual
|
||||
-Wconversion -Wswitch-enum
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow)
|
||||
-Wconversion -Wundef
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
-Wno-dangling-else -Wno-unused-local-typedefs)
|
||||
endif ()
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
|
||||
-Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
|
||||
-Wvector-operation-performance -Wsized-deallocation)
|
||||
-Wvector-operation-performance -Wsized-deallocation -Wshadow)
|
||||
endif ()
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||
@ -123,8 +175,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion
|
||||
-Wno-sign-conversion -Wdeprecated -Wweak-vtables)
|
||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
|
||||
-Wdeprecated -Wweak-vtables -Wshadow
|
||||
-Wno-gnu-zero-variadic-macro-arguments)
|
||||
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||
if (HAS_NULLPTR_WARNING)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
@ -138,7 +191,7 @@ if (MSVC)
|
||||
set(WERROR_FLAG /WX)
|
||||
endif ()
|
||||
|
||||
if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
# If Microsoft SDK is installed create script run-msbuild.bat that
|
||||
# calls SetEnv.cmd to set up build environment and runs msbuild.
|
||||
# It is useful when building Visual Studio projects with the SDK
|
||||
@ -177,9 +230,12 @@ function(add_headers VAR)
|
||||
endfunction()
|
||||
|
||||
# Define the fmt library, its includes and the needed defines.
|
||||
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
|
||||
locale.h os.h ostream.h posix.h printf.h ranges.h)
|
||||
if (FMT_OS)
|
||||
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||
format-inl.h locale.h os.h ostream.h printf.h ranges.h
|
||||
xchar.h)
|
||||
if (FMT_MODULE)
|
||||
set(FMT_SOURCES src/fmt.cc)
|
||||
elseif (FMT_OS)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
else()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
@ -193,7 +249,10 @@ if (HAVE_STRTOD_L)
|
||||
endif ()
|
||||
|
||||
if (MINGW)
|
||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||
check_cxx_compiler_flag("-Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
|
||||
if (${FMT_HAS_MBIG_OBJ})
|
||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
if (FMT_WERROR)
|
||||
@ -202,10 +261,13 @@ endif ()
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (FMT_MODULE)
|
||||
enable_module(fmt)
|
||||
endif ()
|
||||
|
||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt PUBLIC
|
||||
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
@ -223,7 +285,8 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND NOT EMSCRIPTEN)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
|
||||
NOT EMSCRIPTEN)
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(fmt -Wl,--as-needed)
|
||||
@ -240,7 +303,7 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt-header-only INTERFACE
|
||||
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
@ -248,20 +311,22 @@ target_include_directories(fmt-header-only INTERFACE
|
||||
if (FMT_INSTALL)
|
||||
include(CMakePackageConfigHelpers)
|
||||
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||
"Installation directory for cmake files, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
"Installation directory for cmake files, a relative path that "
|
||||
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
|
||||
"path.")
|
||||
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
||||
set(targets_export_name fmt-targets)
|
||||
|
||||
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
"Installation directory for libraries, a relative path that "
|
||||
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
"Installation directory for pkgconfig (.pc) files, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||
"absolute path.")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
@ -282,6 +347,13 @@ if (FMT_INSTALL)
|
||||
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||
|
||||
set(INSTALL_TARGETS fmt fmt-header-only)
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||
# imported targets.
|
||||
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
||||
@ -294,12 +366,6 @@ if (FMT_INSTALL)
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||
NAMESPACE fmt::)
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
|
||||
@ -318,11 +384,17 @@ endif ()
|
||||
# Control fuzzing independent of the unit tests.
|
||||
if (FMT_FUZZ)
|
||||
add_subdirectory(test/fuzzing)
|
||||
|
||||
# The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
|
||||
# mode and make fuzzing practically possible. It is similar to
|
||||
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
|
||||
# avoid interfering with fuzzing of projects that use {fmt}.
|
||||
# See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
|
||||
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
||||
endif ()
|
||||
|
||||
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||
if (MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
# Get the list of ignored files from .gitignore.
|
||||
file (STRINGS ${gitignore} lines)
|
||||
list(REMOVE_ITEM lines /doc/html)
|
||||
|
@ -14,4 +14,7 @@ exceptions:
|
||||
* snake_case should be used instead of UpperCamelCase for function and type
|
||||
names
|
||||
|
||||
All documentation must adhere to the [Google Developer Documentation Style
|
||||
Guide](https://developers.google.com/style).
|
||||
|
||||
Thanks for contributing!
|
||||
|
1526
ChangeLog.rst
1526
ChangeLog.rst
File diff suppressed because it is too large
Load Diff
302
README.rst
302
README.rst
@ -1,54 +1,69 @@
|
||||
{fmt}
|
||||
=====
|
||||
|
||||
.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master
|
||||
:target: https://travis-ci.org/fmtlib/fmt
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
|
||||
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
|
||||
:alt: fmt is continuously fuzzed att oss-fuzz
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
||||
:alt: fmt is continuously fuzzed at oss-fuzz
|
||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
|
||||
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
|
||||
Summary&q=proj%3Dlibfmt&can=1
|
||||
Summary&q=proj%3Dfmt&can=1
|
||||
|
||||
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
||||
:alt: Ask questions at StackOverflow with the tag fmt
|
||||
:target: https://stackoverflow.com/questions/tagged/fmt
|
||||
|
||||
**{fmt}** is an open-source formatting library for C++.
|
||||
It can be used as a safe and fast alternative to (s)printf and iostreams.
|
||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
`Documentation <https://fmt.dev/latest/>`__
|
||||
If you like this project, please consider donating to the BYSOL
|
||||
Foundation that helps victims of political repressions in Belarus:
|
||||
https://bysol.org/en/bs/general/.
|
||||
|
||||
`Documentation <https://fmt.dev>`__
|
||||
|
||||
Q&A: ask questions on `StackOverflow with the tag fmt
|
||||
<https://stackoverflow.com/questions/tagged/fmt>`_.
|
||||
|
||||
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Simple `format API <https://fmt.dev/dev/api.html>`_ with positional arguments
|
||||
* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
|
||||
for localization
|
||||
* Implementation of `C++20 std::format
|
||||
<https://en.cppreference.com/w/cpp/utility/format>`__
|
||||
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
|
||||
of Python's
|
||||
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
||||
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
|
||||
round-trip guarantees
|
||||
* Safe `printf implementation
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
|
||||
the POSIX extension for positional arguments
|
||||
* Extensibility: support for user-defined types
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
||||
extension for positional arguments
|
||||
* Extensibility: `support for user-defined types
|
||||
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
|
||||
* High performance: faster than common standard library implementations of
|
||||
`printf <https://en.cppreference.com/w/cpp/io/c/fprintf>`_,
|
||||
iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ and
|
||||
`Converting a hundred million integers to strings per second
|
||||
``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
|
||||
and `Converting a hundred million integers to strings per second
|
||||
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
||||
* Small code size both in terms of source code (the minimum configuration
|
||||
consists of just three header files, ``core.h``, ``format.h`` and
|
||||
``format-inl.h``) and compiled code. See `Compile time and code bloat`_
|
||||
* Reliability: the library has an extensive set of `unit tests
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed
|
||||
* Small code size both in terms of source code with the minimum configuration
|
||||
consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
|
||||
and compiled code; see `Compile time and code bloat`_
|
||||
* Reliability: the library has an extensive set of `tests
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
|
||||
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
|
||||
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors
|
||||
@ -57,18 +72,17 @@ Features
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||
consistent output across platforms and support for older compilers
|
||||
* Clean warning-free codebase even on high warning levels
|
||||
(``-Wall -Wextra -pedantic``)
|
||||
* Clean warning-free codebase even on high warning levels such as
|
||||
``-Wall -Wextra -pedantic``
|
||||
* Locale-independence by default
|
||||
* Support for wide strings
|
||||
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
|
||||
|
||||
See the `documentation <https://fmt.dev/latest/>`_ for more details.
|
||||
See the `documentation <https://fmt.dev>`_ for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Print ``Hello, world!`` to ``stdout``:
|
||||
**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -78,100 +92,95 @@ Print ``Hello, world!`` to ``stdout``:
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
|
||||
Format a string:
|
||||
**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("The answer is {}.", 42);
|
||||
// s == "The answer is 42."
|
||||
|
||||
Format a string using positional arguments:
|
||||
**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||
// s == "I'd rather be happy than right."
|
||||
|
||||
Print a chrono duration:
|
||||
**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
using namespace std::chrono_literals;
|
||||
fmt::print("Elapsed time: {}", 42ms);
|
||||
using namespace std::literals::chrono_literals;
|
||||
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||
}
|
||||
|
||||
prints "Elapsed time: 42ms".
|
||||
Output::
|
||||
|
||||
Check a format string at compile time:
|
||||
Default format: 42s 100ms
|
||||
strftime-like format: 03:15:30
|
||||
|
||||
**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
// test.cc
|
||||
#include <fmt/format.h>
|
||||
std::string s = format(FMT_STRING("{:d}"), "hello");
|
||||
#include <vector>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
gives a compile-time error because ``d`` is an invalid format specifier for a
|
||||
string.
|
||||
|
||||
Use {fmt} as a safe portable replacement for ``itoa``
|
||||
(`godbolt <https://godbolt.org/g/NXmpU4>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::memory_buffer buf;
|
||||
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
|
||||
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
|
||||
// access the string with to_string(buf) or buf.data()
|
||||
|
||||
Format objects of user-defined types via a simple `extension API
|
||||
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
struct date {
|
||||
int year, month, day;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<date> {
|
||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const date& d, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
|
||||
}
|
||||
};
|
||||
|
||||
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
|
||||
// s == "The date is 2012-12-9"
|
||||
|
||||
Create your own functions similar to `format
|
||||
<https://fmt.dev/latest/api.html#format>`_ and
|
||||
`print <https://fmt.dev/latest/api.html#print>`_
|
||||
which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
// Prints formatted error message.
|
||||
void vreport_error(const char* format, fmt::format_args args) {
|
||||
fmt::print("Error: ");
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
template <typename... Args>
|
||||
void report_error(const char* format, const Args & ... args) {
|
||||
vreport_error(format, fmt::make_format_args(args...));
|
||||
int main() {
|
||||
std::vector<int> v = {1, 2, 3};
|
||||
fmt::print("{}\n", v);
|
||||
}
|
||||
|
||||
report_error("file not found: {}", path);
|
||||
Output::
|
||||
|
||||
Note that ``vreport_error`` is not parameterized on argument types which can
|
||||
improve compile times and reduce code size compared to a fully parameterized
|
||||
version.
|
||||
[1, 2, 3]
|
||||
|
||||
**Check a format string at compile time**
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("{:d}", "I am not a number");
|
||||
|
||||
This gives a compile-time error in C++20 because ``d`` is an invalid format
|
||||
specifier for a string.
|
||||
|
||||
**Write a file from a single thread**
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/os.h>
|
||||
|
||||
int main() {
|
||||
auto out = fmt::output_file("guide.txt");
|
||||
out.print("Don't {}", "Panic");
|
||||
}
|
||||
|
||||
This can be `5 to 9 times faster than fprintf
|
||||
<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
|
||||
|
||||
**Print with colors and text styles**
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/color.h>
|
||||
|
||||
int main() {
|
||||
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
|
||||
"Hello, {}!\n", "world");
|
||||
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
|
||||
fmt::emphasis::underline, "Hello, {}!\n", "мир");
|
||||
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
|
||||
"Hello, {}!\n", "世界");
|
||||
}
|
||||
|
||||
Output on a modern terminal:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/
|
||||
576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
|
||||
|
||||
Benchmarks
|
||||
----------
|
||||
@ -196,14 +205,16 @@ The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||
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 times with output sent to ``/dev/null``; for
|
||||
further details refer to the `source
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
|
||||
|
||||
{fmt} is up to 10x faster than ``std::ostringstream`` and ``sprintf`` on
|
||||
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
|
||||
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||
and faster than `double-conversion <https://github.com/google/double-conversion>`_:
|
||||
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
||||
`ryu <https://github.com/ulfjack/ryu>`_:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
|
||||
:target: https://fmt.dev/unknown_mac64_clang10.0.html
|
||||
.. image:: https://user-images.githubusercontent.com/576385/
|
||||
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
|
||||
:target: https://fmt.dev/unknown_mac64_clang12.0.html
|
||||
|
||||
Compile time and code bloat
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -278,34 +289,46 @@ Then you can run the speed test::
|
||||
or the bloat test::
|
||||
|
||||
$ make bloat-test
|
||||
|
||||
Migrating code
|
||||
--------------
|
||||
|
||||
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
|
||||
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
|
||||
``fmt::print``.
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform
|
||||
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
|
||||
real-time strategy game
|
||||
|
||||
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
an open-source library for mathematical programming
|
||||
|
||||
* `Aseprite <https://github.com/aseprite/aseprite>`_:
|
||||
Animated sprite editor & pixel art tool
|
||||
animated sprite editor & pixel art tool
|
||||
|
||||
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft
|
||||
* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
|
||||
operations suite
|
||||
|
||||
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space
|
||||
* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
|
||||
|
||||
* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
|
||||
|
||||
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
|
||||
* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: A compiler cache
|
||||
* `ccache <https://ccache.dev/>`_: a compiler cache
|
||||
|
||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database management system
|
||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
|
||||
management system
|
||||
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
* `Drake <https://drake.mit.edu/>`_: A planning, control, and analysis toolbox
|
||||
* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
|
||||
for nonlinear dynamical systems (MIT)
|
||||
|
||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||
@ -313,71 +336,82 @@ Projects using this library
|
||||
|
||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||
|
||||
* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
|
||||
logging library with latency in nanoseconds
|
||||
|
||||
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
||||
|
||||
* `Grand Mountain Adventure
|
||||
<https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/>`_:
|
||||
A beautiful open-world ski & snowboarding game
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
* `KBEngine <https://kbengine.org/>`_: An open-source MMOG server engine
|
||||
* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
|
||||
engine
|
||||
|
||||
* `Keypirinha <https://keypirinha.com/>`_: A semantic launcher for Windows
|
||||
* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
|
||||
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
|
||||
|
||||
* `Knuth <https://kth.cash/>`_: High-performance Bitcoin full-node
|
||||
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
||||
|
||||
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
||||
Research programming language for concurrent ownership
|
||||
research programming language for concurrent ownership
|
||||
|
||||
* `MongoDB <https://mongodb.com/>`_: Distributed document database
|
||||
* `MongoDB <https://mongodb.com/>`_: distributed document database
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `OpenSpace <https://openspaceproject.com/>`_: An open-source
|
||||
* `OpenSpace <https://openspaceproject.com/>`_: an open-source
|
||||
astrovisualization framework
|
||||
|
||||
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
||||
An MMO server, compatible with most Ultima Online clients
|
||||
an MMO server, compatible with most Ultima Online clients
|
||||
|
||||
* `PyTorch <https://github.com/pytorch/pytorch>`_: An open-source machine
|
||||
* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
|
||||
learning library
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
|
||||
* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
|
||||
associative database
|
||||
|
||||
* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
|
||||
|
||||
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
|
||||
* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
|
||||
navigation, and executing complex multi-line terminal command sequences
|
||||
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
|
||||
proxy
|
||||
|
||||
* `redpanda <https://vectorized.io/redpanda>`_: A 10x faster Kafka® replacement
|
||||
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
|
||||
for mission critical systems written in C++
|
||||
|
||||
* `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client
|
||||
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
|
||||
library
|
||||
|
||||
* `Salesforce Analytics Cloud
|
||||
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
business intelligence software
|
||||
|
||||
* `Scylla <https://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store
|
||||
* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
|
||||
that can handle 1 million transactions per second on a single server
|
||||
|
||||
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++
|
||||
* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
|
||||
framework for high-performance server applications on modern hardware
|
||||
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
|
||||
|
||||
* `Stellar <https://www.stellar.org/>`_: Financial platform
|
||||
* `Stellar <https://www.stellar.org/>`_: financial platform
|
||||
|
||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
|
||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
|
||||
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
||||
MMORPG framework
|
||||
|
||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: The new Windows
|
||||
Terminal
|
||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
||||
terminal
|
||||
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
@ -435,7 +469,7 @@ Boost Format
|
||||
|
||||
This is a very powerful library which supports both ``printf``-like format
|
||||
strings and positional arguments. Its main drawback is performance. According to
|
||||
various benchmarks it is much slower than other methods considered here. Boost
|
||||
various benchmarks, it is much slower than other methods considered here. Boost
|
||||
Format also has excessive build times and severe code bloat issues (see
|
||||
`Benchmarks`_).
|
||||
|
||||
|
@ -4,10 +4,14 @@ if (NOT DOXYGEN)
|
||||
return ()
|
||||
endif ()
|
||||
|
||||
find_package(PythonInterp QUIET REQUIRED)
|
||||
|
||||
add_custom_target(doc
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
|
||||
${FMT_VERSION}
|
||||
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
||||
DESTINATION share/doc/fmt OPTIONAL
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL
|
||||
PATTERN ".doctrees" EXCLUDE)
|
||||
|
321
doc/api.rst
321
doc/api.rst
@ -6,17 +6,18 @@ API Reference
|
||||
|
||||
The {fmt} library API consists of the following parts:
|
||||
|
||||
* :ref:`fmt/core.h <core-api>`: the core API providing argument handling
|
||||
facilities and a lightweight subset of formatting functions
|
||||
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
|
||||
format string checks, wide string, output iterator and user-defined type
|
||||
support
|
||||
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
|
||||
and tuples
|
||||
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
|
||||
for ``char``/UTF-8 with compile-time checks and minimal dependencies
|
||||
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
|
||||
formatting functions and locale support
|
||||
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
||||
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
||||
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
||||
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
||||
* :ref:`fmt/os.h <os-api>`: system APIs
|
||||
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
|
||||
|
||||
All functions and types provided by the library reside in namespace ``fmt`` and
|
||||
macros have prefix ``FMT_``.
|
||||
@ -26,34 +27,66 @@ macros have prefix ``FMT_``.
|
||||
Core API
|
||||
========
|
||||
|
||||
``fmt/core.h`` defines the core API which provides argument handling facilities
|
||||
and a lightweight subset of formatting functions. In the header-only mode
|
||||
include ``fmt/format.h`` instead of ``fmt/core.h``.
|
||||
``fmt/core.h`` defines the core API which provides main formatting functions for
|
||||
``char``/UTF-8 with compile-time checks. It has minimal include dependencies for
|
||||
better compile times. This header is only beneficial when using {fmt} as a
|
||||
library and not in the header-only mode.
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>`
|
||||
similar to that of Python's `str.format
|
||||
<http://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
||||
They take *format_str* and *args* as arguments.
|
||||
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
||||
They take *fmt* and *args* as arguments.
|
||||
|
||||
*format_str* is a format string that contains literal text and replacement
|
||||
fields surrounded by braces ``{}``. The fields are replaced with formatted
|
||||
arguments in the resulting string. A function taking *format_str* doesn't
|
||||
participate in an overload resolution if the latter is not a string.
|
||||
*fmt* is a format string that contains literal text and replacement fields
|
||||
surrounded by braces ``{}``. The fields are replaced with formatted arguments
|
||||
in the resulting string. `~fmt::format_string` is a format string which can be
|
||||
implicitly constructed from a string literal or a ``constexpr`` string and is
|
||||
checked at compile time in C++20. To pass a runtime format string wrap it in
|
||||
`fmt::runtime`.
|
||||
|
||||
*args* is an argument list representing objects to be formatted.
|
||||
|
||||
.. _format:
|
||||
|
||||
.. doxygenfunction:: format(const S&, Args&&...)
|
||||
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<type_identity_t<Char>>>)
|
||||
.. doxygenfunction:: format(format_string<T...> fmt, T&&... args) -> std::string
|
||||
.. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string
|
||||
|
||||
.. doxygenfunction:: format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt
|
||||
.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string<T...> fmt, T&&... args) -> format_to_n_result<OutputIt>
|
||||
.. doxygenfunction:: formatted_size(format_string<T...> fmt, T&&... args) -> size_t
|
||||
|
||||
.. doxygenstruct:: fmt::format_to_n_result
|
||||
:members:
|
||||
|
||||
.. _print:
|
||||
|
||||
.. doxygenfunction:: print(const S&, Args&&...)
|
||||
.. doxygenfunction:: vprint(string_view, format_args)
|
||||
.. doxygenfunction:: fmt::print(format_string<T...> fmt, T&&... args)
|
||||
.. doxygenfunction:: fmt::vprint(string_view fmt, format_args args)
|
||||
|
||||
.. doxygenfunction:: print(std::FILE *, const S&, Args&&...)
|
||||
.. doxygenfunction:: vprint(std::FILE *, string_view, format_args)
|
||||
.. doxygenfunction:: print(std::FILE *f, format_string<T...> fmt, T&&... args)
|
||||
.. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args)
|
||||
|
||||
Compile-time Format String Checks
|
||||
---------------------------------
|
||||
|
||||
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in
|
||||
and string types as well as user-defined types with ``constexpr`` ``parse``
|
||||
functions in their ``formatter`` specializations.
|
||||
Requires C++14 and is a no-op in C++11.
|
||||
|
||||
.. doxygendefine:: FMT_STRING
|
||||
|
||||
To force the use of compile-time checks, define the preprocessor variable
|
||||
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
||||
will fail to compile with regular strings. Runtime-checked
|
||||
formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc.
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_string
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_string
|
||||
|
||||
.. doxygenfunction:: fmt::runtime(const S&)
|
||||
|
||||
Named Arguments
|
||||
---------------
|
||||
@ -65,6 +98,35 @@ Named arguments are not supported in compile-time checks at the moment.
|
||||
Argument Lists
|
||||
--------------
|
||||
|
||||
You can create your own formatting function with compile-time checks and small
|
||||
binary footprint, for example (https://godbolt.org/z/oba4Mc):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
void vlog(const char* file, int line, fmt::string_view format,
|
||||
fmt::format_args args) {
|
||||
fmt::print("{}: {}: ", file, line);
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void log(const char* file, int line, const S& format, Args&&... args) {
|
||||
vlog(file, line, format,
|
||||
fmt::make_args_checked<Args...>(format, args...));
|
||||
}
|
||||
|
||||
#define MY_LOG(format, ...) \
|
||||
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
|
||||
|
||||
MY_LOG("invalid squishiness: {}", 42);
|
||||
|
||||
Note that ``vlog`` is not parameterized on argument types which improves compile
|
||||
times and reduces binary code size compared to a fully parameterized version.
|
||||
|
||||
.. doxygenfunction:: fmt::make_args_checked(const S&, const remove_reference_t<Args>&...)
|
||||
|
||||
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||
|
||||
.. doxygenclass:: fmt::format_arg_store
|
||||
@ -76,11 +138,16 @@ Argument Lists
|
||||
.. doxygenclass:: fmt::basic_format_args
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: fmt::format_args
|
||||
.. doxygentypedef:: fmt::format_args
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_arg
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_context
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_context
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
@ -88,12 +155,11 @@ Compatibility
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::string_view
|
||||
.. doxygentypedef:: fmt::wstring_view
|
||||
|
||||
Locale
|
||||
------
|
||||
|
||||
All formatting is locale-independent by default. Use the ``'n'`` format
|
||||
All formatting is locale-independent by default. Use the ``'L'`` format
|
||||
specifier to insert the appropriate number separator characters from the
|
||||
locale::
|
||||
|
||||
@ -108,17 +174,10 @@ locale::
|
||||
Format API
|
||||
==========
|
||||
|
||||
``fmt/format.h`` defines the full format API providing compile-time format
|
||||
string checks, wide string, output iterator and user-defined type support.
|
||||
``fmt/format.h`` defines the full format API providing additional formatting
|
||||
functions and locale support.
|
||||
|
||||
Compile-time Format String Checks
|
||||
---------------------------------
|
||||
|
||||
Compile-time checks are supported for built-in and string types as well as
|
||||
user-defined types with ``constexpr`` ``parse`` functions in their ``formatter``
|
||||
specializations.
|
||||
|
||||
.. doxygendefine:: FMT_STRING
|
||||
.. _udt:
|
||||
|
||||
Formatting User-defined Types
|
||||
-----------------------------
|
||||
@ -128,16 +187,16 @@ template and implement ``parse`` and ``format`` methods::
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
struct point { double x, y; };
|
||||
struct point {
|
||||
double x, y;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<point> {
|
||||
template <> struct fmt::formatter<point> {
|
||||
// Presentation format: 'f' - fixed, 'e' - exponential.
|
||||
char presentation = 'f';
|
||||
|
||||
// Parses format specifications of the form ['f' | 'e'].
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
// auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
// [ctx.begin(), ctx.end()) is a character range that contains a part of
|
||||
// the format string starting from the format specifications to be parsed,
|
||||
// e.g. in
|
||||
@ -154,8 +213,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
|
||||
|
||||
// Check if reached the end of the range:
|
||||
if (it != end && *it != '}')
|
||||
throw format_error("invalid format");
|
||||
if (it != end && *it != '}') throw format_error("invalid format");
|
||||
|
||||
// Return an iterator past the end of the parsed range:
|
||||
return it;
|
||||
@ -164,13 +222,11 @@ template and implement ``parse`` and ``format`` methods::
|
||||
// Formats the point p using the parsed format specification (presentation)
|
||||
// stored in this formatter.
|
||||
template <typename FormatContext>
|
||||
auto format(const point& p, FormatContext& ctx) {
|
||||
// auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11
|
||||
auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
// ctx.out() is an output iterator to write to.
|
||||
return format_to(
|
||||
ctx.out(),
|
||||
presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})",
|
||||
p.x, p.y);
|
||||
return presentation == 'f'
|
||||
? format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
|
||||
: format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
|
||||
}
|
||||
};
|
||||
|
||||
@ -237,44 +293,38 @@ You can also write a formatter for a hierarchy of classes::
|
||||
fmt::print("{}", a); // prints "B"
|
||||
}
|
||||
|
||||
If a type provides both a ``formatter`` specialization and an implicit
|
||||
conversion to a formattable type, the specialization takes precedence over the
|
||||
conversion.
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_parse_context
|
||||
:members:
|
||||
|
||||
Output Iterator Support
|
||||
-----------------------
|
||||
|
||||
.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...)
|
||||
.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, const S&, const Args&...)
|
||||
.. doxygenstruct:: fmt::format_to_n_result
|
||||
:members:
|
||||
|
||||
Literal-based API
|
||||
-----------------
|
||||
|
||||
The following user-defined literals are defined in ``fmt/format.h``.
|
||||
|
||||
.. doxygenfunction:: operator""_format(const char *, size_t)
|
||||
.. doxygenfunction:: operator""_format(const char *s, size_t n) -> detail::udl_formatter<char>
|
||||
|
||||
.. doxygenfunction:: operator""_a(const char *, size_t)
|
||||
.. doxygenfunction:: operator""_a(const char *s, size_t) -> detail::udl_arg<char>
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
.. doxygenstruct:: fmt::is_char
|
||||
.. doxygenfunction:: fmt::ptr(T p) -> const void*
|
||||
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
|
||||
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
|
||||
|
||||
.. doxygentypedef:: fmt::char_t
|
||||
.. doxygenfunction:: fmt::to_string(const T &value) -> std::string
|
||||
|
||||
.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...)
|
||||
.. doxygenfunction:: fmt::to_string_view(const Char *s) -> basic_string_view<Char>
|
||||
|
||||
.. doxygenfunction:: fmt::to_string(const T&)
|
||||
.. doxygenfunction:: fmt::join(Range &&range, string_view sep) -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>>
|
||||
|
||||
.. doxygenfunction:: fmt::to_wstring(const T&)
|
||||
.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>
|
||||
|
||||
.. doxygenfunction:: fmt::to_string_view(const Char *)
|
||||
|
||||
.. doxygenfunction:: fmt::join(const Range&, string_view)
|
||||
|
||||
.. doxygenfunction:: fmt::join(It, Sentinel, string_view)
|
||||
.. doxygenfunction:: fmt::group_digits(T value) -> group_digits_view<T>
|
||||
|
||||
.. doxygenclass:: fmt::detail::buffer
|
||||
:members:
|
||||
@ -286,20 +336,14 @@ Utilities
|
||||
System Errors
|
||||
-------------
|
||||
|
||||
fmt does not use ``errno`` to communicate errors to the user, but it may call
|
||||
system functions which set ``errno``. Users should not make any assumptions about
|
||||
the value of ``errno`` being preserved by library functions.
|
||||
{fmt} does not use ``errno`` to communicate errors to the user, but it may call
|
||||
system functions which set ``errno``. Users should not make any assumptions
|
||||
about the value of ``errno`` being preserved by library functions.
|
||||
|
||||
.. doxygenclass:: fmt::system_error
|
||||
:members:
|
||||
.. doxygenfunction:: fmt::system_error
|
||||
|
||||
.. doxygenfunction:: fmt::format_system_error
|
||||
|
||||
.. doxygenclass:: fmt::windows_error
|
||||
:members:
|
||||
|
||||
.. _formatstrings:
|
||||
|
||||
Custom Allocators
|
||||
-----------------
|
||||
|
||||
@ -318,8 +362,8 @@ allocator::
|
||||
|
||||
custom_string vformat(custom_allocator alloc, fmt::string_view format_str,
|
||||
fmt::format_args args) {
|
||||
custom_memory_buffer buf(alloc);
|
||||
fmt::vformat_to(buf, format_str, args);
|
||||
auto buf = custom_memory_buffer(alloc);
|
||||
fmt::vformat_to(std::back_inserter(buf), format_str, args);
|
||||
return custom_string(buf.data(), buf.size(), alloc);
|
||||
}
|
||||
|
||||
@ -330,10 +374,10 @@ allocator::
|
||||
return vformat(alloc, format_str, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
The allocator will be used for the output container only. If you are using named
|
||||
arguments, the container that stores pointers to them will be allocated using
|
||||
the default allocator. Also floating-point formatting falls back on ``sprintf``
|
||||
which may do allocations.
|
||||
The allocator will be used for the output container only. Formatting functions
|
||||
normally don't do any allocations for built-in and string types except for
|
||||
non-default floating-point formatting that occasionally falls back on
|
||||
``sprintf``.
|
||||
|
||||
.. _ranges-api:
|
||||
|
||||
@ -365,41 +409,84 @@ Using ``fmt::join``, you can separate tuple elements with a custom separator::
|
||||
Date and Time Formatting
|
||||
========================
|
||||
|
||||
The library supports `strftime
|
||||
<http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like date and time
|
||||
formatting::
|
||||
``fmt/chrono.h`` provides formatters for
|
||||
|
||||
* `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
|
||||
* `std::chrono::time_point
|
||||
<https://en.cppreference.com/w/cpp/chrono/time_point>`_
|
||||
* `std::tm <https://en.cppreference.com/w/cpp/chrono/c/tm>`_
|
||||
|
||||
The format syntax is described in :ref:`chrono-specs`.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
std::time_t t = std::time(nullptr);
|
||||
// Prints "The date is 2016-04-29." (with the current date)
|
||||
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
||||
int main() {
|
||||
std::time_t t = std::time(nullptr);
|
||||
|
||||
The format string syntax is described in the documentation of
|
||||
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
|
||||
// Prints "The date is 2020-11-07." (with the current date):
|
||||
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
// Prints "Default format: 42s 100ms":
|
||||
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||
|
||||
// Prints "strftime-like format: 03:15:30":
|
||||
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||
}
|
||||
|
||||
.. doxygenfunction:: localtime(std::time_t time)
|
||||
|
||||
.. doxygenfunction:: gmtime(std::time_t time)
|
||||
|
||||
.. _compile-api:
|
||||
|
||||
Format string compilation
|
||||
=========================
|
||||
|
||||
``fmt/compile.h`` provides format string compilation support. Format strings
|
||||
are parsed at compile time and converted into efficient formatting code. This
|
||||
supports arguments of built-in and string types as well as user-defined types
|
||||
with ``constexpr`` ``parse`` functions in their ``formatter`` specializations.
|
||||
Format string compilation can generate more binary code compared to the default
|
||||
API and is only recommended in places where formatting is a performance
|
||||
bottleneck.
|
||||
``fmt/compile.h`` provides format string compilation support when using
|
||||
``FMT_COMPILE``. Format strings 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 ``constexpr`` ``parse`` functions in
|
||||
their ``formatter`` specializations. Format string compilation can generate more
|
||||
binary code compared to the default API and is only recommended in places where
|
||||
formatting is a performance bottleneck.
|
||||
|
||||
.. doxygendefine:: FMT_COMPILE
|
||||
|
||||
.. _color-api:
|
||||
|
||||
Terminal color and text style
|
||||
=============================
|
||||
|
||||
``fmt/color.h`` provides support for terminal color and text style output.
|
||||
|
||||
.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args)
|
||||
|
||||
.. doxygenfunction:: fg(detail::color_type)
|
||||
|
||||
.. doxygenfunction:: bg(detail::color_type)
|
||||
|
||||
.. _os-api:
|
||||
|
||||
System APIs
|
||||
===========
|
||||
|
||||
.. doxygenclass:: fmt::ostream
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: fmt::windows_error
|
||||
:members:
|
||||
|
||||
.. _ostream-api:
|
||||
|
||||
``std::ostream`` Support
|
||||
========================
|
||||
|
||||
``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
|
||||
user-defined types that have overloaded ``operator<<``::
|
||||
user-defined types that have an overloaded insertion operator (``operator<<``)::
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
@ -416,7 +503,10 @@ user-defined types that have overloaded ``operator<<``::
|
||||
std::string s = fmt::format("The date is {}", date(2012, 12, 9));
|
||||
// s == "The date is 2012-12-9"
|
||||
|
||||
.. doxygenfunction:: print(std::basic_ostream<Char>&, const S&, Args&&...)
|
||||
{fmt} only supports insertion operators that are defined in the same namespaces
|
||||
as the types they format and can be found with the argument-dependent lookup.
|
||||
|
||||
.. doxygenfunction:: print(std::basic_ostream<Char> &os, const S &format_str, Args&&... args)
|
||||
|
||||
.. _printf-api:
|
||||
|
||||
@ -425,18 +515,32 @@ user-defined types that have overloaded ``operator<<``::
|
||||
|
||||
The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
|
||||
The following functions use `printf format string syntax
|
||||
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||
<https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||
the POSIX extension for positional arguments. Unlike their standard
|
||||
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
|
||||
argument type doesn't match its format specification.
|
||||
|
||||
.. doxygenfunction:: printf(const S&, const Args&...)
|
||||
.. doxygenfunction:: printf(const S &format_str, const T&... args)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::FILE *, const S&, const Args&...)
|
||||
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
|
||||
|
||||
.. doxygenfunction:: fprintf(std::basic_ostream<Char>&, const S&, const Args&...)
|
||||
.. doxygenfunction:: sprintf(const S&, const T&...)
|
||||
|
||||
.. doxygenfunction:: sprintf(const S&, const Args&...)
|
||||
.. _xchar-api:
|
||||
|
||||
``wchar_t`` Support
|
||||
===================
|
||||
|
||||
The optional header ``fmt/xchar.h`` provides support for ``wchar_t`` and exotic
|
||||
character types.
|
||||
|
||||
.. doxygenstruct:: fmt::is_char
|
||||
|
||||
.. doxygentypedef:: fmt::wstring_view
|
||||
|
||||
.. doxygentypedef:: fmt::wformat_context
|
||||
|
||||
.. doxygenfunction:: fmt::to_wstring(const T &value)
|
||||
|
||||
Compatibility with C++20 ``std::format``
|
||||
========================================
|
||||
@ -447,9 +551,6 @@ differences:
|
||||
|
||||
* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
|
||||
collisions with standard library implementations.
|
||||
* The ``'L'`` format specifier cannot be combined with presentation specifiers
|
||||
yet.
|
||||
* Width calculation doesn't use grapheme clusterization. The latter has been
|
||||
implemented in a separate branch but hasn't been integrated yet.
|
||||
* Chrono formatting doesn't support C++20 date types since they are not provided
|
||||
by standard library implementations.
|
||||
* Most C++20 chrono types are not supported yet.
|
||||
|
@ -90,12 +90,14 @@
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
LINK_SUFFIX: '{{ link_suffix }}',
|
||||
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }},
|
||||
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
|
||||
};
|
||||
</script>
|
||||
{%- for scriptfile in script_files %}
|
||||
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||
{{ js_tag(scriptfile) }}
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
|
118
doc/build.py
118
doc/build.py
@ -1,63 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# Build the documentation.
|
||||
|
||||
from __future__ import print_function
|
||||
import errno, os, shutil, sys, tempfile
|
||||
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
|
||||
from distutils.version import LooseVersion
|
||||
import errno, os, re, sys
|
||||
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
|
||||
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1']
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1']
|
||||
|
||||
def pip_install(package, commit=None, **kwargs):
|
||||
"Install package using pip."
|
||||
min_version = kwargs.get('min_version')
|
||||
if min_version:
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
try:
|
||||
installed_version = get_distribution(os.path.basename(package)).version
|
||||
if LooseVersion(installed_version) >= min_version:
|
||||
print('{} {} already installed'.format(package, min_version))
|
||||
return
|
||||
except DistributionNotFound:
|
||||
pass
|
||||
if commit:
|
||||
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
|
||||
print('Installing {0}'.format(package))
|
||||
check_call(['pip', 'install', package])
|
||||
class Pip:
|
||||
def __init__(self, venv_dir):
|
||||
self.path = os.path.join(venv_dir, 'bin', 'pip')
|
||||
|
||||
def create_build_env(dirname='virtualenv'):
|
||||
def install(self, package, commit=None):
|
||||
"Install package using pip."
|
||||
if commit:
|
||||
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
|
||||
print('Installing {0}'.format(package))
|
||||
check_call([self.path, 'install', package])
|
||||
|
||||
def create_build_env(venv_dir='virtualenv'):
|
||||
# Create virtualenv.
|
||||
if not os.path.exists(dirname):
|
||||
check_call(['virtualenv', dirname])
|
||||
import sysconfig
|
||||
scripts_dir = os.path.basename(sysconfig.get_path('scripts'))
|
||||
activate_this_file = os.path.join(dirname, scripts_dir, 'activate_this.py')
|
||||
with open(activate_this_file) as f:
|
||||
exec(f.read(), dict(__file__=activate_this_file))
|
||||
# Import get_distribution after activating virtualenv to get info about
|
||||
# the correct packages.
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
# Upgrade pip because installation of sphinx with pip 1.1 available on Travis
|
||||
# is broken (see #207) and it doesn't support the show command.
|
||||
pip_version = get_distribution('pip').version
|
||||
if LooseVersion(pip_version) < LooseVersion('1.5.4'):
|
||||
print("Updating pip")
|
||||
check_call(['pip', 'install', '--upgrade', 'pip'])
|
||||
# Upgrade distribute because installation of sphinx with distribute 0.6.24
|
||||
# available on Travis is broken (see #207).
|
||||
try:
|
||||
distribute_version = get_distribution('distribute').version
|
||||
if LooseVersion(distribute_version) <= LooseVersion('0.6.24'):
|
||||
print("Updating distribute")
|
||||
check_call(['pip', 'install', '--upgrade', 'distribute'])
|
||||
except DistributionNotFound:
|
||||
pass
|
||||
# Install Sphinx and Breathe.
|
||||
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee',
|
||||
min_version='1.4.1.dev20160531')
|
||||
pip_install('michaeljones/breathe',
|
||||
'129222318f7c8f865d2631e7da7b033567e7f56a',
|
||||
min_version='4.2.0')
|
||||
if not os.path.exists(venv_dir):
|
||||
check_call(['python3', '-m', 'venv', venv_dir])
|
||||
# Install Sphinx and Breathe. Require the exact version of Sphinx which is
|
||||
# compatible with Breathe.
|
||||
pip = Pip(venv_dir)
|
||||
pip.install('wheel')
|
||||
pip.install('six')
|
||||
# See: https://github.com/sphinx-doc/sphinx/issues/9777
|
||||
pip.install('docutils==0.17.1')
|
||||
pip.install('sphinx-doc/sphinx', 'v3.3.0')
|
||||
pip.install('michaeljones/breathe', 'v4.25.0')
|
||||
|
||||
def build_docs(version='dev', **kwargs):
|
||||
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
|
||||
@ -66,16 +38,17 @@ def build_docs(version='dev', **kwargs):
|
||||
'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt'))
|
||||
# Build docs.
|
||||
cmd = ['doxygen', '-']
|
||||
p = Popen(cmd, stdin=PIPE)
|
||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
||||
doxyxml_dir = os.path.join(work_dir, 'doxyxml')
|
||||
p.communicate(input=r'''
|
||||
out, _ = p.communicate(input=r'''
|
||||
PROJECT_NAME = fmt
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_MAN = NO
|
||||
GENERATE_RTF = NO
|
||||
CASE_SENSE_NAMES = NO
|
||||
INPUT = {0}/core.h {0}/compile.h {0}/format.h {0}/os.h \
|
||||
{0}/ostream.h {0}/printf.h {0}/time.h
|
||||
INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
|
||||
{0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \
|
||||
{0}/xchar.h
|
||||
QUIET = YES
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
AUTOLINK_SUPPORT = NO
|
||||
@ -86,6 +59,8 @@ def build_docs(version='dev', **kwargs):
|
||||
ALIASES += "endrst=\endverbatim"
|
||||
MACRO_EXPANSION = YES
|
||||
PREDEFINED = _WIN32=1 \
|
||||
__linux__=1 \
|
||||
FMT_ENABLE_IF(...)= \
|
||||
FMT_USE_VARIADIC_TEMPLATES=1 \
|
||||
FMT_USE_RVALUE_REFERENCES=1 \
|
||||
FMT_USE_USER_DEFINED_LITERALS=1 \
|
||||
@ -94,20 +69,39 @@ def build_docs(version='dev', **kwargs):
|
||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||
"FMT_END_NAMESPACE=}}" \
|
||||
"FMT_STRING_ALIAS=1" \
|
||||
"FMT_ENABLE_IF(B)="
|
||||
EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str
|
||||
"FMT_VARIADIC(...)=" \
|
||||
"FMT_VARIADIC_W(...)=" \
|
||||
"FMT_DOC=1"
|
||||
EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \
|
||||
fmt::basic_format_arg::handle
|
||||
'''.format(include_dir, doxyxml_dir).encode('UTF-8'))
|
||||
out = out.decode('utf-8')
|
||||
internal_symbols = [
|
||||
'fmt::detail::.*',
|
||||
'basic_data<>',
|
||||
'fmt::type_identity',
|
||||
'fmt::dynamic_formatter'
|
||||
]
|
||||
noisy_warnings = [
|
||||
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
|
||||
') is not documented.',
|
||||
'warning: Internal inconsistency: .* does not belong to any container!'
|
||||
]
|
||||
for w in noisy_warnings:
|
||||
out = re.sub('.*' + w + '\n', '', out)
|
||||
print(out)
|
||||
if p.returncode != 0:
|
||||
raise CalledProcessError(p.returncode, cmd)
|
||||
|
||||
html_dir = os.path.join(work_dir, 'html')
|
||||
main_versions = reversed(versions[-3:])
|
||||
check_call(['sphinx-build',
|
||||
check_call([os.path.join(work_dir, 'virtualenv', 'bin', 'sphinx-build'),
|
||||
'-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir),
|
||||
'-Dversion=' + version, '-Drelease=' + version,
|
||||
'-Aversion=' + version, '-Aversions=' + ','.join(main_versions),
|
||||
'-b', 'html', doc_dir, html_dir])
|
||||
try:
|
||||
check_call(['lessc', '--clean-css',
|
||||
check_call(['lessc', '--verbose', '--clean-css',
|
||||
'--include-path=' + os.path.join(doc_dir, 'bootstrap'),
|
||||
os.path.join(doc_dir, 'fmt.less'),
|
||||
os.path.join(html_dir, '_static', 'fmt.css')])
|
||||
|
@ -56,6 +56,11 @@ div.sphinxsidebar {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Override center alignment in tables.
|
||||
td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
@ -23,24 +23,26 @@ Format API
|
||||
|
||||
The format API is similar in spirit to the C ``printf`` family of function but
|
||||
is safer, simpler and several times `faster
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
|
||||
<https://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
||||
than common standard library implementations.
|
||||
The `format string syntax <syntax.html>`_ is similar to the one used by
|
||||
`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_ in
|
||||
`str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ in
|
||||
Python:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::format("The answer is {}.", 42);
|
||||
std::string s = fmt::format("The answer is {}.", 42);
|
||||
|
||||
The ``fmt::format`` function returns a string "The answer is 42.". You can use
|
||||
``fmt::memory_buffer`` to avoid constructing ``std::string``:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::memory_buffer out;
|
||||
format_to(out, "For a moment, {} happened.", "nothing");
|
||||
out.data(); // returns a pointer to the formatted data
|
||||
auto out = fmt::memory_buffer();
|
||||
format_to(std::back_inserter(out),
|
||||
"For a moment, {} happened.", "nothing");
|
||||
auto data = out.data(); // pointer to the formatted data
|
||||
auto size = out.size(); // size of the formatted data
|
||||
|
||||
The ``fmt::print`` function performs formatting and writes the result to a stream:
|
||||
|
||||
@ -48,21 +50,19 @@ The ``fmt::print`` function performs formatting and writes the result to a strea
|
||||
|
||||
fmt::print(stderr, "System error code = {}\n", errno);
|
||||
|
||||
The file argument can be omitted in which case the function prints to
|
||||
``stdout``:
|
||||
If you omit the file argument the function will print to ``stdout``:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("Don't {}\n", "panic");
|
||||
|
||||
The Format API also supports positional arguments useful for localization:
|
||||
The format API also supports positional arguments useful for localization:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("I'd rather be {1} than {0}.", "right", "happy");
|
||||
|
||||
Named arguments can be created with ``fmt::arg``. This makes it easier to track
|
||||
what goes where when multiple arguments are being formatted:
|
||||
You can pass named arguments with ``fmt::arg``:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -91,16 +91,17 @@ time. For example, the code
|
||||
|
||||
fmt::format("The answer is {:d}", "forty-two");
|
||||
|
||||
throws a ``format_error`` exception with description "unknown format code 'd' for
|
||||
string", because the argument ``"forty-two"`` is a string while the format code
|
||||
``d`` only applies to integers, while
|
||||
throws the ``format_error`` exception because the argument ``"forty-two"`` is a
|
||||
string while the format code ``d`` only applies to integers.
|
||||
|
||||
The code
|
||||
|
||||
.. code:: c++
|
||||
|
||||
format(FMT_STRING("The answer is {:d}"), "forty-two");
|
||||
|
||||
reports a compile-time error for the same reason on compilers that support
|
||||
relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for details.
|
||||
reports a compile-time error on compilers that support relaxed ``constexpr``.
|
||||
See `here <api.html#c.fmt>`_ for details.
|
||||
|
||||
The following code
|
||||
|
||||
@ -109,21 +110,15 @@ The following code
|
||||
fmt::format("Cyrillic letter {}", L'\x42e');
|
||||
|
||||
produces a compile-time error because wide character ``L'\x42e'`` cannot be
|
||||
formatted into a narrow string. You can use a wide format string instead:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::format(L"Cyrillic letter {}", L'\x42e');
|
||||
|
||||
For comparison, writing a wide character to ``std::ostream`` results in
|
||||
its numeric value being written to the stream (i.e. 1070 instead of letter 'ю'
|
||||
which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is
|
||||
needed.
|
||||
formatted into a narrow string. For comparison, writing a wide character to
|
||||
``std::ostream`` results in its numeric value being written to the stream
|
||||
(i.e. 1070 instead of letter 'ю' which is represented by ``L'\x42e'`` if we
|
||||
use Unicode) which is rarely desirable.
|
||||
|
||||
Compact Binary Code
|
||||
-------------------
|
||||
|
||||
The library is designed to produce compact per-call compiled code. For example
|
||||
The library produces compact per-call compiled code. For example
|
||||
(`godbolt <https://godbolt.org/g/TZU4KF>`_),
|
||||
|
||||
.. code:: c++
|
||||
@ -144,8 +139,8 @@ compiles to just
|
||||
mov rcx, rsp
|
||||
mov edi, offset .L.str
|
||||
mov esi, 17
|
||||
mov edx, 2
|
||||
call fmt::v5::vprint(fmt::v5::basic_string_view<char>, fmt::v5::format_args)
|
||||
mov edx, 1
|
||||
call fmt::v7::vprint(fmt::v7::basic_string_view<char>, fmt::v7::format_args)
|
||||
xor eax, eax
|
||||
add rsp, 24
|
||||
ret
|
||||
@ -167,20 +162,19 @@ The library is highly portable and relies only on a small set of C++11 features:
|
||||
* deleted functions
|
||||
* alias templates
|
||||
|
||||
These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older
|
||||
compilers use {fmt} `version 4.x
|
||||
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be
|
||||
maintained and only requires C++98.
|
||||
These are available in GCC 4.8, Clang 3.4, MSVC 19.0 (2015) and more recent
|
||||
compiler version. For older compilers use {fmt} `version 4.x
|
||||
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which is maintained and
|
||||
only requires C++98.
|
||||
|
||||
The output of all formatting functions is consistent across platforms. In
|
||||
particular, formatting a floating-point infinity always gives ``inf`` while the
|
||||
output of ``printf`` is platform-dependent. For example,
|
||||
The output of all formatting functions is consistent across platforms.
|
||||
For example,
|
||||
|
||||
.. code::
|
||||
|
||||
fmt::print("{}", std::numeric_limits<double>::infinity());
|
||||
|
||||
always prints ``inf``.
|
||||
always prints ``inf`` while the output of ``printf`` is platform-dependent.
|
||||
|
||||
.. _ease-of-use:
|
||||
|
||||
@ -192,11 +186,13 @@ just three header files and no external dependencies.
|
||||
A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows
|
||||
using the library both in open-source and commercial projects.
|
||||
|
||||
`Learn more... <contents.html>`_
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a>
|
||||
|
||||
<div class="section footer">
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=fmtlib&repo=fmt&type=watch&count=true"
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=fmtlib&repo=fmt&type=watch&count=true"
|
||||
class="github-btn" width="100" height="20"></iframe>
|
||||
</div>
|
||||
|
115
doc/syntax.rst
115
doc/syntax.rst
@ -16,7 +16,7 @@ literal text, it can be escaped by doubling: ``{{`` and ``}}``.
|
||||
The grammar for a replacement field is as follows:
|
||||
|
||||
.. productionlist:: sf
|
||||
replacement_field: "{" [`arg_id`] [":" `format_spec`] "}"
|
||||
replacement_field: "{" [`arg_id`] [":" (`format_spec` | `chrono_format_spec`)] "}"
|
||||
arg_id: `integer` | `identifier`
|
||||
integer: `digit`+
|
||||
digit: "0"..."9"
|
||||
@ -27,8 +27,8 @@ The grammar for a replacement field is as follows:
|
||||
In less formal terms, the replacement field can start with an *arg_id*
|
||||
that specifies the argument whose value is to be formatted and inserted into
|
||||
the output instead of the replacement field.
|
||||
The *arg_id* is optionally followed by a *format_spec*, which is preceded
|
||||
by a colon ``':'``. These specify a non-default format for the replacement value.
|
||||
The *arg_id* is optionally followed by a *format_spec*, which is preceded by a
|
||||
colon ``':'``. These specify a non-default format for the replacement value.
|
||||
|
||||
See also the :ref:`formatspec` section.
|
||||
|
||||
@ -75,14 +75,14 @@ although some of the formatting options are only supported by the numeric types.
|
||||
The general form of a *standard format specifier* is:
|
||||
|
||||
.. productionlist:: sf
|
||||
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`]
|
||||
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`]["L"][`type`]
|
||||
fill: <a character other than '{' or '}'>
|
||||
align: "<" | ">" | "^"
|
||||
sign: "+" | "-" | " "
|
||||
width: `integer` | "{" [`arg_id`] "}"
|
||||
precision: `integer` | "{" [`arg_id`] "}"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
||||
type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" |
|
||||
: "o" | "p" | "s" | "x" | "X"
|
||||
|
||||
The *fill* character can be any Unicode code point other than ``'{'`` or
|
||||
``'}'``. The presence of a fill character is signaled by the character following
|
||||
@ -112,18 +112,18 @@ meaning in this case.
|
||||
The *sign* option is only valid for number types, and can be one of the
|
||||
following:
|
||||
|
||||
+---------+----------------------------------------------------------+
|
||||
| Option | Meaning |
|
||||
+=========+==========================================================+
|
||||
| ``'+'`` | indicates that a sign should be used for both |
|
||||
| | positive as well as negative numbers. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'-'`` | indicates that a sign should be used only for negative |
|
||||
| | numbers (this is the default behavior). |
|
||||
+---------+----------------------------------------------------------+
|
||||
| space | indicates that a leading space should be used on |
|
||||
| | positive numbers, and a minus sign on negative numbers. |
|
||||
+---------+----------------------------------------------------------+
|
||||
+---------+------------------------------------------------------------+
|
||||
| Option | Meaning |
|
||||
+=========+============================================================+
|
||||
| ``'+'`` | indicates that a sign should be used for both |
|
||||
| | nonnegative as well as negative numbers. |
|
||||
+---------+------------------------------------------------------------+
|
||||
| ``'-'`` | indicates that a sign should be used only for negative |
|
||||
| | numbers (this is the default behavior). |
|
||||
+---------+------------------------------------------------------------+
|
||||
| space | indicates that a leading space should be used on |
|
||||
| | nonnegative numbers, and a minus sign on negative numbers. |
|
||||
+---------+------------------------------------------------------------+
|
||||
|
||||
The ``'#'`` option causes the "alternate form" to be used for the
|
||||
conversion. The alternate form is defined differently for different
|
||||
@ -161,7 +161,11 @@ displayed after the decimal point for a floating-point value formatted with
|
||||
value formatted with ``'g'`` or ``'G'``. For non-number types the field
|
||||
indicates the maximum field size - in other words, how many characters will be
|
||||
used from the field content. The *precision* is not allowed for integer,
|
||||
character, Boolean, and pointer values.
|
||||
character, Boolean, and pointer values. Note that a C string must be
|
||||
null-terminated even if precision is specified.
|
||||
|
||||
The ``'L'`` option uses the current locale setting to insert the appropriate
|
||||
number separator characters. This option is only valid for numeric types.
|
||||
|
||||
Finally, the *type* determines how the data should be presented.
|
||||
|
||||
@ -200,6 +204,8 @@ The available integer presentation types are:
|
||||
| | ``'#'`` option with this type adds the prefix ``"0B"`` |
|
||||
| | to the output value. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'c'`` | Character format. Outputs the number as a character. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'d'`` | Decimal integer. Outputs the number in base 10. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'o'`` | Octal format. Outputs the number in base 8. |
|
||||
@ -214,10 +220,6 @@ The available integer presentation types are:
|
||||
| | ``'#'`` option with this type adds the prefix ``"0X"`` |
|
||||
| | to the output value. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'L'`` | Locale-specific format. This is the same as ``'d'``, |
|
||||
| | except that it uses the current locale setting to insert |
|
||||
| | the appropriate number separator characters. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| none | The same as ``'d'``. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
@ -261,14 +263,8 @@ The available presentation types for floating-point values are:
|
||||
| | ``'E'`` if the number gets too large. The |
|
||||
| | representations of infinity and NaN are uppercased, too. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'L'`` | Locale-specific format. This is the same as ``'g'``, |
|
||||
| | except that it uses the current locale setting to insert |
|
||||
| | the appropriate number separator characters. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| none | Similar to ``'g'``, except that fixed-point notation, |
|
||||
| | when used, has at least one digit past the decimal |
|
||||
| | point. The default precision is as high as needed to |
|
||||
| | represent the particular value. |
|
||||
| none | Similar to ``'g'``, except that the default precision is |
|
||||
| | as high as needed to represent the particular value. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
.. ifconfig:: False
|
||||
@ -303,6 +299,59 @@ The available presentation types for pointers are:
|
||||
| none | The same as ``'p'``. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
.. _chrono-specs:
|
||||
|
||||
Chrono Format Specifications
|
||||
============================
|
||||
|
||||
Format specifications for chrono types have the following syntax:
|
||||
|
||||
.. productionlist:: sf
|
||||
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
|
||||
chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char`
|
||||
conversion_spec: "%" [`modifier`] `chrono_type`
|
||||
literal_char: <a character other than '{', '}' or '%'>
|
||||
modifier: "E" | "O"
|
||||
chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" |
|
||||
: "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" |
|
||||
: "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" |
|
||||
: "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%"
|
||||
|
||||
Literal chars are copied unchanged to the output. Precision is valid only for
|
||||
``std::chrono::duration`` types with a floating-point representation type.
|
||||
|
||||
The available presentation types (*chrono_type*) for chrono durations and time
|
||||
points are:
|
||||
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| Type | Meaning |
|
||||
+=========+====================================================================+
|
||||
| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a |
|
||||
| | single digit, it is prefixed with 0. The modified command ``%OH`` |
|
||||
| | produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'M'`` | The minute as a decimal number. If the result is a single digit, |
|
||||
| | it is prefixed with 0. The modified command ``%OM`` produces the |
|
||||
| | locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
|
||||
| | 10, the result is prefixed with 0. If the precision of the input |
|
||||
| | cannot be exactly 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 input (or to a microseconds |
|
||||
| | precision if the conversion to floating-point decimal seconds |
|
||||
| | cannot be made within 18 fractional digits). The character for the |
|
||||
| | decimal point is localized according to the locale. The modified |
|
||||
| | command ``%OS`` produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
|
||||
Specifiers that have a calendaric component such as `'d'` (the day of month)
|
||||
are valid only for ``std::tm`` and not durations or time points.
|
||||
|
||||
``std::tm`` uses the system's `strftime
|
||||
<https://en.cppreference.com/w/cpp/chrono/c/strftime>`_ so refer to its
|
||||
documentation for details on supported conversion specifiers.
|
||||
|
||||
.. _formatexamples:
|
||||
|
||||
Format Examples
|
||||
@ -391,7 +440,7 @@ Using type-specific formatting::
|
||||
|
||||
auto t = tm();
|
||||
t.tm_year = 2010 - 1900;
|
||||
t.tm_mon = 6;
|
||||
t.tm_mon = 7;
|
||||
t.tm_mday = 4;
|
||||
t.tm_hour = 12;
|
||||
t.tm_min = 15;
|
||||
@ -401,7 +450,7 @@ Using type-specific formatting::
|
||||
|
||||
Using the comma as a thousands separator::
|
||||
|
||||
#include <fmt/locale.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
|
||||
// s == "1,234,567,890"
|
||||
|
@ -15,7 +15,7 @@ Building the Library
|
||||
|
||||
The included `CMake build script`__ can be used to build the fmt
|
||||
library on a wide range of platforms. CMake is freely available for
|
||||
download from http://www.cmake.org/download/.
|
||||
download from https://www.cmake.org/download/.
|
||||
|
||||
__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt
|
||||
|
||||
@ -50,7 +50,15 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
|
||||
|
||||
cmake -DBUILD_SHARED_LIBS=TRUE ...
|
||||
|
||||
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
||||
__ https://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
||||
|
||||
|
||||
To build a `static library` with position independent code (required if the main
|
||||
consumer of the fmt library is a shared library i.e. a Python extension) set the
|
||||
``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``::
|
||||
|
||||
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ...
|
||||
|
||||
|
||||
Installing the Library
|
||||
======================
|
||||
@ -83,6 +91,49 @@ Setting up your target to use a header-only version of ``fmt`` is equally easy::
|
||||
|
||||
target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only)
|
||||
|
||||
Usage with build2
|
||||
=================
|
||||
|
||||
You can use `build2 <https://build2.org>`_, a dependency manager and a
|
||||
build-system combined, to use ``fmt``.
|
||||
|
||||
Currently this package is available in these package repositories:
|
||||
|
||||
- **https://cppget.org/fmt/** for released and published versions.
|
||||
- `The git repository with the sources of the build2 package of fmt <https://github.com/build2-packaging/fmt.git>`_
|
||||
for unreleased or custom revisions of ``fmt``.
|
||||
|
||||
**Usage:**
|
||||
|
||||
- ``build2`` package name: ``fmt``
|
||||
- Library target name : ``lib{fmt}``
|
||||
|
||||
For example, to make your ``build2`` project depend on ``fmt``:
|
||||
|
||||
- Add one of the repositories to your configurations, or in your
|
||||
``repositories.manifest``, if not already there::
|
||||
|
||||
:
|
||||
role: prerequisite
|
||||
location: https://pkg.cppget.org/1/stable
|
||||
|
||||
- Add this package as a dependency to your ``./manifest`` file
|
||||
(example for ``v7.0.x``)::
|
||||
|
||||
depends: fmt ~7.0.0
|
||||
|
||||
- Import the target and use it as a prerequisite to your own target
|
||||
using `fmt` in the appropriate ``buildfile``::
|
||||
|
||||
import fmt = fmt%lib{fmt}
|
||||
lib{mylib} : cxx{**} ... $fmt
|
||||
|
||||
Then build your project as usual with `b` or `bdep update`.
|
||||
|
||||
For ``build2`` newcomers or to get more details and use cases, you can read the
|
||||
``build2``
|
||||
`toolchain introduction <https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml>`_.
|
||||
|
||||
Building the Documentation
|
||||
==========================
|
||||
|
||||
@ -130,6 +181,18 @@ The fmt port in vcpkg is kept up to date by Microsoft team members and community
|
||||
contributors. If the version is out of date, please `create an issue or pull
|
||||
request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
|
||||
|
||||
LHelper
|
||||
=======
|
||||
|
||||
You can download and install fmt using
|
||||
`lhelper <https://github.com/franko/lhelper>`__ dependency manager::
|
||||
|
||||
lhelper activate <some-environment>
|
||||
lhelper install fmt
|
||||
|
||||
All the recipes for lhelper are kept in the
|
||||
`lhelper's recipe <https://github.com/franko/lhelper-recipes>`__ repository.
|
||||
|
||||
Android NDK
|
||||
===========
|
||||
|
||||
@ -139,11 +202,11 @@ For an example of using fmt with Android NDK, see the
|
||||
`android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_
|
||||
repository.
|
||||
|
||||
__ https://github.com/fmtlib/fmt/blob/master/Android.mk
|
||||
__ https://github.com/fmtlib/fmt/blob/master/support/Android.mk
|
||||
|
||||
Homebrew
|
||||
========
|
||||
|
||||
fmt can be installed on OS X using `Homebrew <http://brew.sh/>`_::
|
||||
fmt can be installed on OS X using `Homebrew <https://brew.sh/>`_::
|
||||
|
||||
brew install fmt
|
||||
|
234
include/fmt/args.h
Normal file
234
include/fmt/args.h
Normal file
@ -0,0 +1,234 @@
|
||||
// Formatting library for C++ - dynamic format arguments
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_ARGS_H_
|
||||
#define FMT_ARGS_H_
|
||||
|
||||
#include <functional> // std::reference_wrapper
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <vector>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||
|
||||
template <typename T> const T& unwrap(const T& v) { return v; }
|
||||
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
|
||||
return static_cast<const T&>(v);
|
||||
}
|
||||
|
||||
class dynamic_arg_list {
|
||||
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||
// templates it doesn't complain about inability to deduce single translation
|
||||
// unit for placing vtable. So storage_node_base is made a fake template.
|
||||
template <typename = void> struct node {
|
||||
virtual ~node() = default;
|
||||
std::unique_ptr<node<>> next;
|
||||
};
|
||||
|
||||
template <typename T> struct typed_node : node<> {
|
||||
T value;
|
||||
|
||||
template <typename Arg>
|
||||
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
|
||||
: value(arg.data(), arg.size()) {}
|
||||
};
|
||||
|
||||
std::unique_ptr<node<>> head_;
|
||||
|
||||
public:
|
||||
template <typename T, typename Arg> const T& push(const Arg& arg) {
|
||||
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||
auto& value = new_node->value;
|
||||
new_node->next = std::move(head_);
|
||||
head_ = std::move(new_node);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
\rst
|
||||
A dynamic version of `fmt::format_arg_store`.
|
||||
It's equipped with a storage to potentially temporary objects which lifetimes
|
||||
could be shorter than the format arguments object.
|
||||
|
||||
It can be implicitly converted into `~fmt::basic_format_args` for passing
|
||||
into type-erased formatting functions such as `~fmt::vformat`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Context>
|
||||
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:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
template <typename T> struct need_copy {
|
||||
static constexpr detail::type mapped_type =
|
||||
detail::mapped_type_constant<T, Context>::value;
|
||||
|
||||
enum {
|
||||
value = !(detail::is_reference_wrapper<T>::value ||
|
||||
std::is_same<T, basic_string_view<char_type>>::value ||
|
||||
std::is_same<T, detail::std_string_view<char_type>>::value ||
|
||||
(mapped_type != detail::type::cstring_type &&
|
||||
mapped_type != detail::type::string_type &&
|
||||
mapped_type != detail::type::custom_type))
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using stored_type = conditional_t<detail::is_string<T>::value &&
|
||||
!has_formatter<T, Context>::value &&
|
||||
!detail::is_reference_wrapper<T>::value,
|
||||
std::basic_string<char_type>, T>;
|
||||
|
||||
// Storage of basic_format_arg must be contiguous.
|
||||
std::vector<basic_format_arg<Context>> data_;
|
||||
std::vector<detail::named_arg_info<char_type>> named_info_;
|
||||
|
||||
// Storage of arguments not fitting into basic_format_arg must grow
|
||||
// without relocation because items in data_ refer to it.
|
||||
detail::dynamic_arg_list dynamic_args_;
|
||||
|
||||
friend class basic_format_args<Context>;
|
||||
|
||||
unsigned long long get_types() const {
|
||||
return detail::is_unpacked_bit | data_.size() |
|
||||
(named_info_.empty()
|
||||
? 0ULL
|
||||
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||
}
|
||||
|
||||
const basic_format_arg<Context>* data() const {
|
||||
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||
}
|
||||
|
||||
template <typename T> void emplace_arg(const T& arg) {
|
||||
data_.emplace_back(detail::make_arg<Context>(arg));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||
if (named_info_.empty()) {
|
||||
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
||||
data_.insert(data_.begin(), {zero_ptr, 0});
|
||||
}
|
||||
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
||||
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||
data->pop_back();
|
||||
};
|
||||
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||
guard{&data_, pop_one};
|
||||
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
||||
guard.release();
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr dynamic_format_arg_store() = default;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Adds an argument into the dynamic store for later passing to a formatting
|
||||
function.
|
||||
|
||||
Note that custom types and string types (but not string views) are copied
|
||||
into the store dynamically allocating memory if necessary.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc");
|
||||
store.push_back(1.5f);
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
\endrst
|
||||
*/
|
||||
template <typename T> void push_back(const T& arg) {
|
||||
if (detail::const_check(need_copy<T>::value))
|
||||
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
||||
else
|
||||
emplace_arg(detail::unwrap(arg));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Adds a reference to the argument into the dynamic store for later passing to
|
||||
a formatting function.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char band[] = "Rolling Stones";
|
||||
store.push_back(std::cref(band));
|
||||
band[9] = 'c'; // Changing str affects the output.
|
||||
std::string result = fmt::vformat("{}", store);
|
||||
// result == "Rolling Scones"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T> void push_back(std::reference_wrapper<T> arg) {
|
||||
static_assert(
|
||||
need_copy<T>::value,
|
||||
"objects of built-in types and string views are always copied");
|
||||
emplace_arg(arg.get());
|
||||
}
|
||||
|
||||
/**
|
||||
Adds named argument into the dynamic store for later passing to a formatting
|
||||
function. ``std::reference_wrapper`` is supported to avoid copying of the
|
||||
argument. The name is always copied into the store.
|
||||
*/
|
||||
template <typename T>
|
||||
void push_back(const detail::named_arg<char_type, T>& arg) {
|
||||
const char_type* arg_name =
|
||||
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||
if (detail::const_check(need_copy<T>::value)) {
|
||||
emplace_arg(
|
||||
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
||||
} else {
|
||||
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||
}
|
||||
}
|
||||
|
||||
/** Erase all elements from the store */
|
||||
void clear() {
|
||||
data_.clear();
|
||||
named_info_.clear();
|
||||
dynamic_args_ = detail::dynamic_arg_list();
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Reserves space to store at least *new_cap* arguments including
|
||||
*new_cap_named* named arguments.
|
||||
\endrst
|
||||
*/
|
||||
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||
FMT_ASSERT(new_cap >= new_cap_named,
|
||||
"Set of arguments includes set of named arguments");
|
||||
data_.reserve(new_cap);
|
||||
named_info_.reserve(new_cap_named);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_ARGS_H_
|
1404
include/fmt/chrono.h
1404
include/fmt/chrono.h
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,15 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// __declspec(deprecated) is broken in some MSVC versions.
|
||||
#if FMT_MSC_VER
|
||||
# define FMT_DEPRECATED_NONMSVC
|
||||
#else
|
||||
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
@ -177,9 +185,13 @@ enum class terminal_color : uint8_t {
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
faint = 1 << 1,
|
||||
italic = 1 << 2,
|
||||
underline = 1 << 3,
|
||||
blink = 1 << 4,
|
||||
reverse = 1 << 5,
|
||||
conceal = 1 << 6,
|
||||
strikethrough = 1 << 7,
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
@ -198,7 +210,7 @@ struct rgb {
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
@ -221,9 +233,10 @@ struct color_type {
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// Experimental text formatting support.
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
/** A text style consisting of foreground and background colors and emphasis. */
|
||||
class text_style {
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||
@ -260,33 +273,14 @@ class text_style {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND 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)
|
||||
FMT_THROW(format_error("can't AND 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;
|
||||
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
|
||||
const text_style& rhs) {
|
||||
return and_assign(rhs);
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs &= rhs;
|
||||
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
|
||||
operator&(text_style lhs, const text_style& rhs) {
|
||||
return lhs.and_assign(rhs);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||
@ -326,8 +320,34 @@ class text_style {
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND 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)
|
||||
FMT_THROW(format_error("can't AND 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;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
@ -338,19 +358,22 @@ class text_style {
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/true, foreground);
|
||||
/** Creates a text style from the foreground (text) color. */
|
||||
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(true, foreground);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/false, background);
|
||||
/** Creates a text style from the background color. */
|
||||
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
|
||||
emphasis rhs) FMT_NOEXCEPT {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
@ -358,7 +381,7 @@ template <typename Char> struct ansi_color_escape {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
bool is_background = esc == detail::data::background_color;
|
||||
bool is_background = esc == string_view("\x1b[48;2;");
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
@ -390,16 +413,18 @@ template <typename Char> struct ansi_color_escape {
|
||||
buffer[19] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
|
||||
uint8_t em_codes[4] = {};
|
||||
uint8_t em_bits = static_cast<uint8_t>(em);
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
uint8_t em_codes[num_emphases] = {};
|
||||
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
|
||||
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
|
||||
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
|
||||
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
|
||||
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
|
||||
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
|
||||
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
|
||||
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
|
||||
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (size_t i = 0; i < num_emphases; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
@ -411,12 +436,13 @@ template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
static constexpr size_t num_emphases = 8;
|
||||
Char buffer[7u + 3u * num_emphases + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
@ -425,18 +451,22 @@ template <typename Char> struct ansi_color_escape {
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
static FMT_CONSTEXPR bool has_emphasis(emphasis em,
|
||||
emphasis mask) FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
|
||||
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
detail::color_type background) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(background, detail::data::background_color);
|
||||
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
@ -455,24 +485,23 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(detail::data::reset_color, stream);
|
||||
fputs("\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(detail::data::wreset_color, stream);
|
||||
fputs(L"\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
const char* begin = data::reset_color;
|
||||
const char* end = begin + sizeof(data::reset_color) - 1;
|
||||
buffer.append(begin, end);
|
||||
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
buffer.append(reset_color.begin(), reset_color.end());
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
@ -489,14 +518,15 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
detail::vformat_to(buf, format_str, args, {});
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
@ -504,28 +534,34 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
Example:
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = buffer_context<char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
||||
vprint(f, ts, format_str,
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify text formatting.
|
||||
Example:
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
@ -557,10 +593,46 @@ inline std::basic_string<Char> vformat(
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return vformat(ts, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
return fmt::vformat(ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string with the given text_style and writes the output to ``out``.
|
||||
*/
|
||||
template <typename OutputIt, typename Char,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
OutputIt vformat_to(
|
||||
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, ts, format_str, args);
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments with the given text_style, writes the result to the output
|
||||
iterator ``out`` and returns the iterator past the end of the output range.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::vector<char> out;
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||
detail::is_string<S>::value>
|
||||
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
||||
|
File diff suppressed because it is too large
Load Diff
2789
include/fmt/core.h
2789
include/fmt/core.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4751
include/fmt/format.h
4751
include/fmt/format.h
File diff suppressed because it is too large
Load Diff
@ -1,78 +1,2 @@
|
||||
// Formatting library for C++ - std::locale support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_LOCALE_H_
|
||||
#define FMT_LOCALE_H_
|
||||
|
||||
#include <locale>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
template <typename Char>
|
||||
typename buffer_context<Char>::iterator vformat_to(
|
||||
const std::locale& loc, buffer<Char>& buf,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
|
||||
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
|
||||
args, detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
std::basic_string<Char> vformat(
|
||||
const std::locale& loc, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(loc, buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const std::locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
return detail::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
return detail::vformat(
|
||||
loc, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = enable_if_t<
|
||||
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
|
||||
inline OutputIt vformat_to(
|
||||
OutputIt out, const std::locale& loc, const S& format_str,
|
||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
||||
using af = detail::arg_formatter<OutputIt, Char>;
|
||||
return vformat_to<af>(out, to_string_view(format_str), args,
|
||||
detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
|
||||
detail::is_string<S>::value)>
|
||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = format_context_t<OutputIt, char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
return vformat_to(out, loc, to_string_view(format_str),
|
||||
basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_LOCALE_H_
|
||||
#include "xchar.h"
|
||||
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
|
||||
|
291
include/fmt/os.h
291
include/fmt/os.h
@ -8,16 +8,12 @@
|
||||
#ifndef FMT_OS_H_
|
||||
#define FMT_OS_H_
|
||||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <cerrno>
|
||||
#include <clocale> // for locale_t
|
||||
#include <clocale> // locale_t
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // for strtod_l
|
||||
#include <cstdlib> // strtod_l
|
||||
#include <system_error> // std::system_error
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
@ -25,16 +21,20 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_USE_FCNTL
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if FMT_HAS_INCLUDE("fcntl.h") && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
# endif
|
||||
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || \
|
||||
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
# else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
@ -73,6 +73,7 @@
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
/**
|
||||
\rst
|
||||
@ -121,19 +122,28 @@ template <typename Char> class basic_cstring_view {
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
private:
|
||||
int value_;
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
public:
|
||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write_bytes(out, ec.category().name(),
|
||||
basic_format_specs<Char>());
|
||||
out = detail::write<Char>(out, Char(':'));
|
||||
out = detail::write<Char>(out, ec.value());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace detail {
|
||||
FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
@ -142,7 +152,7 @@ class utf16_to_utf8 {
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(wstring_view s);
|
||||
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||
size_t size() const { return buffer_.size() - 1; }
|
||||
const char* c_str() const { return &buffer_[0]; }
|
||||
@ -151,59 +161,68 @@ class utf16_to_utf8 {
|
||||
// Performs conversion returning a system error code instead of
|
||||
// throwing exception on conversion error. This method may still throw
|
||||
// in case of memory allocation error.
|
||||
FMT_API int convert(wstring_view s);
|
||||
FMT_API int convert(basic_string_view<wchar_t> s);
|
||||
};
|
||||
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
} // namespace detail
|
||||
const char* message) FMT_NOEXCEPT;
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
/** A Windows error. */
|
||||
class windows_error : public system_error {
|
||||
private:
|
||||
FMT_API void init(int error_code, string_view format_str, format_args args);
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||
format_args args);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`fmt::windows_error` object with the description
|
||||
of the form
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`std::system_error` object with the description
|
||||
of the form
|
||||
|
||||
.. parsed-literal::
|
||||
*<message>*: *<system-message>*
|
||||
.. parsed-literal::
|
||||
*<message>*: *<system-message>*
|
||||
|
||||
where *<message>* is the formatted message and *<system-message>* is the
|
||||
system message corresponding to the error code.
|
||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||
If *error_code* is not a valid error code such as -1, the system message
|
||||
will look like "error -1".
|
||||
where *<message>* is the formatted message and *<system-message>* is the
|
||||
system message corresponding to the error code.
|
||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||
If *error_code* is not a valid error code such as -1, the system message
|
||||
will look like "error -1".
|
||||
|
||||
**Example**::
|
||||
**Example**::
|
||||
|
||||
// This throws a windows_error with the description
|
||||
// cannot open file 'madeup': The system cannot find the file specified.
|
||||
// or similar (system message may vary).
|
||||
const char *filename = "madeup";
|
||||
LPOFSTRUCT of = LPOFSTRUCT();
|
||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
if (file == HFILE_ERROR) {
|
||||
throw fmt::windows_error(GetLastError(),
|
||||
"cannot open file '{}'", filename);
|
||||
}
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
windows_error(int error_code, string_view message, const Args&... args) {
|
||||
init(error_code, message, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
// This throws a system_error with the description
|
||||
// cannot open file 'madeup': The system cannot find the file specified.
|
||||
// or similar (system message may vary).
|
||||
const char *filename = "madeup";
|
||||
LPOFSTRUCT of = LPOFSTRUCT();
|
||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
if (file == HFILE_ERROR) {
|
||||
throw fmt::windows_error(GetLastError(),
|
||||
"cannot open file '{}'", filename);
|
||||
}
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::system_error windows_error(int error_code, string_view message,
|
||||
const Args&... args) {
|
||||
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
// Reports a Windows error without throwing an exception.
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
const char* message) FMT_NOEXCEPT;
|
||||
#else
|
||||
inline const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
return std::system_category();
|
||||
}
|
||||
#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.
|
||||
class buffered_file {
|
||||
private:
|
||||
@ -254,7 +273,7 @@ class buffered_file {
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
vprint(format_str, fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
@ -278,7 +297,9 @@ class file {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
|
||||
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
|
||||
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
|
||||
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
@ -293,7 +314,8 @@ class file {
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
file& operator=(file&& other) FMT_NOEXCEPT {
|
||||
// Move assignment is not noexcept because close may throw.
|
||||
file& operator=(file&& other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
@ -329,7 +351,7 @@ class file {
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
|
||||
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
@ -343,62 +365,116 @@ class file {
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
class direct_buffered_file;
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args);
|
||||
struct buffer_size {
|
||||
buffer_size() = default;
|
||||
size_t value = 0;
|
||||
buffer_size operator=(size_t val) const {
|
||||
auto bs = buffer_size();
|
||||
bs.value = val;
|
||||
return bs;
|
||||
}
|
||||
};
|
||||
|
||||
// A buffered file with a direct buffer access and no synchronization.
|
||||
class direct_buffered_file {
|
||||
struct ostream_params {
|
||||
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||
|
||||
ostream_params() {}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||
oflag = new_oflag;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, detail::buffer_size bs)
|
||||
: ostream_params(params...) {
|
||||
this->buffer_size = bs.value;
|
||||
}
|
||||
|
||||
// Intel has a bug that results in failure to deduce a constructor
|
||||
// for empty parameter packs.
|
||||
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
|
||||
ostream_params(int new_oflag) : oflag(new_oflag) {}
|
||||
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
|
||||
# endif
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
// Added {} below to work around default constructor error known to
|
||||
// occur in Xcode versions 7.2.1 and 8.2.1.
|
||||
constexpr detail::buffer_size buffer_size{};
|
||||
|
||||
/** A fast output stream which is not thread-safe. */
|
||||
class FMT_API ostream final : private detail::buffer<char> {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
enum { buffer_size = 4096 };
|
||||
char buffer_[buffer_size];
|
||||
int pos_;
|
||||
void grow(size_t) override;
|
||||
|
||||
void flush() {
|
||||
if (pos_ == 0) return;
|
||||
file_.write(buffer_, pos_);
|
||||
pos_ = 0;
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
|
||||
int free_capacity() const { return buffer_size - pos_; }
|
||||
|
||||
public:
|
||||
direct_buffered_file(cstring_view path, int oflag)
|
||||
: file_(path, oflag), pos_(0) {}
|
||||
|
||||
~direct_buffered_file() {
|
||||
flush();
|
||||
ostream(ostream&& other)
|
||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
~ostream() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
clear();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
friend void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args) {
|
||||
// We could avoid double buffering.
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buf), format_str, args...);
|
||||
auto remaining_pos = 0;
|
||||
auto remaining_size = buf.size();
|
||||
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
|
||||
auto size = f.free_capacity();
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
|
||||
f.pos_ += size;
|
||||
f.flush();
|
||||
remaining_pos += size;
|
||||
remaining_size -= size;
|
||||
}
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
|
||||
f.pos_ += static_cast<int>(remaining_size);
|
||||
/**
|
||||
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||
output to the file.
|
||||
*/
|
||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Opens a file for writing. Supported parameters passed in *params*:
|
||||
|
||||
* ``<integer>``: Flags passed to `open
|
||||
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
|
||||
(``file::WRONLY | file::CREATE`` by default)
|
||||
* ``buffer_size=<integer>``: Output buffer size
|
||||
|
||||
**Example**::
|
||||
|
||||
auto out = fmt::output_file("guide.txt");
|
||||
out.print("Don't {}", "Panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline ostream output_file(cstring_view path, T... params) {
|
||||
return {path, detail::ostream_params(params...)};
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
@ -436,7 +512,7 @@ class locale {
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
FMT_DEPRECATED double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
@ -445,6 +521,7 @@ class locale {
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
||||
|
@ -14,71 +14,44 @@
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void_t<> operator<<(null<>);
|
||||
void_t<> operator<<(const Char*);
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
||||
!std::is_enum<T>::value)>
|
||||
void_t<> operator<<(T);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
// Checks if T has a user-defined operator<<.
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
static auto test(int)
|
||||
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
|
||||
<< std::declval<U>()) != 0>;
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
template <typename> static auto test(...) -> std::false_type;
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
is_streamable() = default;
|
||||
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Formatting of built-in types and arrays is intentionally disabled because
|
||||
// it's handled by standard (non-ostream) formatters.
|
||||
template <typename T, typename Char>
|
||||
struct is_streamable<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
std::is_arithmetic<T>::value || std::is_array<T>::value ||
|
||||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
|
||||
std::is_same<T, std::basic_string<Char>>::value ||
|
||||
std::is_same<T, std_string_view<Char>>::value ||
|
||||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
|
||||
: std::false_type {};
|
||||
|
||||
// Write the content of buf to os.
|
||||
// It is a separate function rather than a part of vprint to simplify testing.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
@ -96,53 +69,47 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& output = std::basic_ostream<Char>(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.resize(buf.size());
|
||||
buf.try_resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
using formatter<basic_string_view<Char>, Char>::parse;
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
return formatter<basic_string_view<Char>, Char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
@ -156,11 +123,12 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
@ -1,2 +0,0 @@
|
||||
#include "os.h"
|
||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
@ -10,11 +10,54 @@
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <ostream>
|
||||
|
||||
#include "ostream.h"
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
private:
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
|
||||
public:
|
||||
using char_type = Char;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments are
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
detail::error_handler().on_error(message);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
@ -178,81 +221,38 @@ template <typename Char> class printf_width_handler {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// For printing into memory_buffer.
|
||||
template <typename Char, typename Context>
|
||||
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
|
||||
basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return detail::vprintf(buf, format, args);
|
||||
}
|
||||
using detail::vprintf;
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
/**
|
||||
\rst
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
// The ``printf`` argument formatter.
|
||||
template <typename OutputIt, typename Char>
|
||||
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
||||
public:
|
||||
using iterator = OutputIt;
|
||||
|
||||
class printf_arg_formatter : public arg_formatter<Char> {
|
||||
private:
|
||||
using char_type = Char;
|
||||
using base = detail::arg_formatter_base<OutputIt, Char>;
|
||||
using base = arg_formatter<Char>;
|
||||
using context_type = basic_printf_context<OutputIt, Char>;
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
void write_null_pointer(char) {
|
||||
this->specs()->type = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
void write_null_pointer(wchar_t) {
|
||||
this->specs()->type = 0;
|
||||
this->write(L"(nil)");
|
||||
OutputIt write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = presentation_type::none;
|
||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
public:
|
||||
using format_specs = typename base::format_specs;
|
||||
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
||||
: base{iter, s, locale_ref()}, context_(ctx) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*buffer* is a reference to the output buffer and *specs* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
||||
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
|
||||
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
if (std::is_same<T, bool>::value) {
|
||||
format_specs& fmt_specs = *this->specs();
|
||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
|
||||
fmt_specs.type = 0;
|
||||
this->write(value != 0);
|
||||
} else if (std::is_same<T, char_type>::value) {
|
||||
format_specs& fmt_specs = *this->specs();
|
||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||
// std::is_same instead.
|
||||
if (std::is_same<T, Char>::value) {
|
||||
format_specs fmt_specs = this->specs;
|
||||
if (fmt_specs.type != presentation_type::none &&
|
||||
fmt_specs.type != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
@ -260,138 +260,49 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
}
|
||||
return this->out();
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
OutputIt operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
iterator operator()(const char* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->specs()->type == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write("(null)");
|
||||
return this->out();
|
||||
OutputIt operator()(const char* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
iterator operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->specs()->type == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write(L"(null)");
|
||||
return this->out();
|
||||
OutputIt operator()(const wchar_t* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
iterator operator()(basic_string_view<char_type> value) {
|
||||
OutputIt operator()(basic_string_view<Char> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
iterator operator()(monostate value) { return base::operator()(value); }
|
||||
|
||||
/** Formats a pointer. */
|
||||
iterator operator()(const void* value) {
|
||||
if (value) return base::operator()(value);
|
||||
this->specs()->type = 0;
|
||||
write_null_pointer(char_type());
|
||||
return this->out();
|
||||
OutputIt operator()(const void* value) {
|
||||
return value ? base::operator()(value) : write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
handle.format(context_.parse_context(), context_);
|
||||
return this->out();
|
||||
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx =
|
||||
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
||||
handle.format(parse_ctx, context_);
|
||||
return this->out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct printf_formatter {
|
||||
printf_formatter() = delete;
|
||||
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
detail::format_value(detail::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
This template formats data and writes the output through an output iterator.
|
||||
*/
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
using char_type = Char;
|
||||
using iterator = OutputIt;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
private:
|
||||
using format_specs = basic_format_specs<char_type>;
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
parse_context_type parse_ctx_;
|
||||
|
||||
static void parse_flags(format_specs& specs, const Char*& it,
|
||||
const Char* end);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
format_arg get_arg(int arg_index = -1);
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments are
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args), parse_ctx_(format_str) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
parse_context_type& parse_context() { return parse_ctx_; }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
parse_ctx_.on_error(message);
|
||||
}
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
const Char*& it,
|
||||
const Char* end) {
|
||||
template <typename Char>
|
||||
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||
const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
@ -417,35 +328,24 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
return detail::get_arg(*this, arg_index);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
const Char* end,
|
||||
format_specs& specs) {
|
||||
template <typename Char, typename GetArg>
|
||||
int parse_header(const Char*& it, const Char* end,
|
||||
basic_format_specs<Char>& specs, GetArg get_arg) {
|
||||
int arg_index = -1;
|
||||
char_type c = *it;
|
||||
Char c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
detail::error_handler eh;
|
||||
int value = parse_nonnegative_int(it, end, eh);
|
||||
int value = parse_nonnegative_int(it, end, -1);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
arg_index = value != -1 ? value : max_value<int>();
|
||||
} else {
|
||||
if (c == '0') specs.fill[0] = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
if (value == -1) FMT_THROW(format_error("number is too big"));
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
@ -455,58 +355,76 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
specs.width = parse_nonnegative_int(it, end, -1);
|
||||
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
detail::printf_width_handler<char_type>(specs), get_arg()));
|
||||
detail::printf_width_handler<Char>(specs), get_arg(-1)));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
template <typename ArgFormatter>
|
||||
OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
auto out = this->out();
|
||||
const Char* start = parse_ctx_.begin();
|
||||
const Char* end = parse_ctx_.end();
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
using OutputIt = buffer_appender<Char>;
|
||||
auto out = OutputIt(buf);
|
||||
auto context = basic_printf_context<OutputIt, Char>(out, args);
|
||||
auto parse_ctx = basic_printf_parse_context<Char>(format);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
auto get_arg = [&](int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx.next_arg_id();
|
||||
else
|
||||
parse_ctx.check_arg_id(--arg_index);
|
||||
return detail::get_arg(context, arg_index);
|
||||
};
|
||||
|
||||
const Char* start = parse_ctx.begin();
|
||||
const Char* end = parse_ctx.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (!detail::find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
||||
break;
|
||||
}
|
||||
Char c = *it++;
|
||||
if (it != end && *it == c) {
|
||||
out = std::copy(start, it, out);
|
||||
out = detail::write(
|
||||
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = std::copy(start, it - 1, out);
|
||||
out = detail::write(out, basic_string_view<Char>(
|
||||
start, detail::to_unsigned(it - 1 - start)));
|
||||
|
||||
format_specs specs;
|
||||
basic_format_specs<Char> specs;
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument not found");
|
||||
int arg_index = parse_header(it, end, specs, get_arg);
|
||||
if (arg_index == 0) parse_ctx.on_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
||||
specs.precision = parse_nonnegative_int(it, end, 0);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg()));
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg 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
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral())
|
||||
@ -516,9 +434,10 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
|
||||
str,
|
||||
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
|
||||
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
|
||||
basic_string_view<Char>(
|
||||
str, detail::to_unsigned(nul != str_end ? nul - str
|
||||
: specs.precision)));
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
@ -532,7 +451,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
char_type t = it != end ? *it : 0;
|
||||
Char t = it != end ? *it : 0;
|
||||
using detail::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
@ -573,32 +492,38 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
char type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
switch (type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
|
||||
arg);
|
||||
visit_format_arg(
|
||||
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
specs.type = parse_presentation_type(type);
|
||||
if (specs.type == presentation_type::none)
|
||||
parse_ctx.on_error("invalid type specifier");
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
||||
out = visit_format_arg(
|
||||
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
|
||||
}
|
||||
return std::copy(start, it, out);
|
||||
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
}
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
@ -612,9 +537,9 @@ using wprintf_args = basic_format_args<wprintf_context>;
|
||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline format_arg_store<printf_context, Args...> make_printf_args(
|
||||
const Args&... args) {
|
||||
template <typename... T>
|
||||
inline auto make_printf_args(const T&... args)
|
||||
-> format_arg_store<printf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
@ -624,18 +549,19 @@ inline format_arg_store<printf_context, Args...> make_printf_args(
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
|
||||
const Args&... args) {
|
||||
template <typename... T>
|
||||
inline auto make_wprintf_args(const T&... args)
|
||||
-> format_arg_store<wprintf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vsprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
inline auto vsprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
@ -648,19 +574,20 @@ inline std::basic_string<Char> vsprintf(
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
template <typename S, typename... T,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(format), make_format_args<context>(args...));
|
||||
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(
|
||||
std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
inline auto vfprintf(
|
||||
std::FILE* f, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
@ -676,19 +603,19 @@ inline int vfprintf(
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(format),
|
||||
make_format_args<context>(args...));
|
||||
return vfprintf(f, to_string_view(fmt),
|
||||
fmt::make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
return vfprintf(stdout, to_string_view(format), args);
|
||||
inline auto vprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
return vfprintf(stdout, to_string_view(fmt), args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -700,52 +627,31 @@ inline int vprintf(
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline int printf(const S& format_str, const Args&... args) {
|
||||
using context = basic_printf_context_t<char_t<S>>;
|
||||
return vprintf(to_string_view(format_str),
|
||||
make_format_args<context>(args...));
|
||||
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||
return vprintf(
|
||||
to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
FMT_DEPRECATED auto vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
detail::write_buffer(os, buffer);
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/** Formats arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter, typename Char,
|
||||
typename Context =
|
||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
||||
typename ArgFormatter::iterator vprintf(
|
||||
detail::buffer<Char>& out, basic_string_view<Char> format_str,
|
||||
basic_format_args<type_identity_t<Context>> args) {
|
||||
typename ArgFormatter::iterator iter(out);
|
||||
Context(iter, format_str, args).template format<ArgFormatter>();
|
||||
return iter;
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(os, to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
|
||||
const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(os, to_string_view(format_str),
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
||||
|
@ -13,47 +13,13 @@
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// output only up to N items from the range.
|
||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> struct formatting_base {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_range : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
|
||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
|
||||
// range.
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_tuple : formatting_base<Char> {
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
@ -75,8 +41,14 @@ OutputIterator copy(char ch, OutputIterator out) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T> class is_like_std_string {
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns true if T has a std::string-like interface, like std::string_view.
|
||||
template <typename T> class is_std_string_like {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
@ -84,26 +56,118 @@ template <typename T> class is_like_std_string {
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
is_string<T>::value ||
|
||||
std::is_convertible<T, std_string_view<char>>::value ||
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename T> class is_map {
|
||||
template <typename U> static auto check(U*) -> typename U::mapped_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
#ifdef FMT_FORMAT_MAP_AS_LIST
|
||||
static FMT_CONSTEXPR_DECL const bool value = false;
|
||||
#else
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename T> class is_set {
|
||||
template <typename U> static auto check(U*) -> typename U::key_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
#ifdef FMT_FORMAT_SET_AS_LIST
|
||||
static FMT_CONSTEXPR_DECL const bool value = false;
|
||||
#else
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 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
|
||||
template <typename T, std::size_t N>
|
||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||
return arr;
|
||||
}
|
||||
template <typename T, std::size_t N>
|
||||
auto range_end(const T (&arr)[N]) -> const T* {
|
||||
return arr + N;
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_member_fn_begin_end_t : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<
|
||||
T, conditional_t<false,
|
||||
conditional_helper<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>,
|
||||
void>> : std::true_type {};
|
||||
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>>
|
||||
: std::true_type {};
|
||||
|
||||
// Member function overload
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||
|
||||
// ADL overload. Only participates in overload resolution if member functions
|
||||
// are not found.
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng)
|
||||
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(begin(static_cast<T&&>(rng)))> {
|
||||
return begin(static_cast<T&&>(rng));
|
||||
}
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(end(static_cast<T&&>(rng)))> {
|
||||
return end(static_cast<T&&>(rng));
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_const_begin_end : std::false_type {};
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_mutable_begin_end : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_const_begin_end<
|
||||
T,
|
||||
void_t<
|
||||
decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
|
||||
decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_mutable_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||
decltype(detail::range_end(std::declval<T>())),
|
||||
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<T, void>
|
||||
: std::integral_constant<bool, (has_const_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 U>
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
@ -157,30 +221,320 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
return add_space ? " {}" : "{}";
|
||||
template <typename Range>
|
||||
using value_type =
|
||||
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||
|
||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||
*out++ = ',';
|
||||
*out++ = ' ';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
struct singleton {
|
||||
unsigned char upper;
|
||||
unsigned char lower_count;
|
||||
};
|
||||
|
||||
inline auto is_printable(uint16_t x, const singleton* singletons,
|
||||
size_t singletons_size,
|
||||
const unsigned char* singleton_lowers,
|
||||
const unsigned char* normal, size_t normal_size)
|
||||
-> bool {
|
||||
auto upper = x >> 8;
|
||||
auto lower_start = 0;
|
||||
for (size_t i = 0; i < singletons_size; ++i) {
|
||||
auto s = singletons[i];
|
||||
auto lower_end = lower_start + s.lower_count;
|
||||
if (upper < s.upper) break;
|
||||
if (upper == s.upper) {
|
||||
for (auto j = lower_start; j < lower_end; ++j) {
|
||||
if (singleton_lowers[j] == (x & 0xff)) return false;
|
||||
}
|
||||
}
|
||||
lower_start = lower_end;
|
||||
}
|
||||
|
||||
auto xsigned = static_cast<int>(x);
|
||||
auto current = true;
|
||||
for (size_t i = 0; i < normal_size; ++i) {
|
||||
auto v = static_cast<int>(normal[i]);
|
||||
auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v;
|
||||
xsigned -= len;
|
||||
if (xsigned < 0) break;
|
||||
current = !current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
||||
// Returns true iff the code point cp is printable.
|
||||
// This code is generated by support/printable.py.
|
||||
inline auto is_printable(uint32_t cp) -> bool {
|
||||
static constexpr singleton singletons0[] = {
|
||||
{0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8},
|
||||
{0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13},
|
||||
{0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5},
|
||||
{0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22},
|
||||
{0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3},
|
||||
{0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8},
|
||||
{0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9},
|
||||
};
|
||||
static constexpr unsigned char singletons0_lower[] = {
|
||||
0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90,
|
||||
0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f,
|
||||
0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1,
|
||||
0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04,
|
||||
0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d,
|
||||
0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf,
|
||||
0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
|
||||
0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d,
|
||||
0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d,
|
||||
0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d,
|
||||
0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5,
|
||||
0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7,
|
||||
0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49,
|
||||
0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7,
|
||||
0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7,
|
||||
0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e,
|
||||
0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16,
|
||||
0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e,
|
||||
0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f,
|
||||
0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf,
|
||||
0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0,
|
||||
0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27,
|
||||
0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91,
|
||||
0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7,
|
||||
0xfe, 0xff,
|
||||
};
|
||||
static constexpr singleton singletons1[] = {
|
||||
{0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2},
|
||||
{0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5},
|
||||
{0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5},
|
||||
{0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2},
|
||||
{0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5},
|
||||
{0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2},
|
||||
{0xfa, 2}, {0xfb, 1},
|
||||
};
|
||||
static constexpr unsigned char singletons1_lower[] = {
|
||||
0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07,
|
||||
0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36,
|
||||
0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87,
|
||||
0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
|
||||
0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b,
|
||||
0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9,
|
||||
0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66,
|
||||
0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27,
|
||||
0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc,
|
||||
0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7,
|
||||
0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6,
|
||||
0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c,
|
||||
0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66,
|
||||
0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0,
|
||||
0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93,
|
||||
};
|
||||
static constexpr unsigned char normal0[] = {
|
||||
0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04,
|
||||
0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0,
|
||||
0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01,
|
||||
0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03,
|
||||
0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03,
|
||||
0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a,
|
||||
0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15,
|
||||
0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f,
|
||||
0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80,
|
||||
0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07,
|
||||
0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06,
|
||||
0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04,
|
||||
0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac,
|
||||
0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c,
|
||||
0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11,
|
||||
0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c,
|
||||
0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b,
|
||||
0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6,
|
||||
0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03,
|
||||
0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80,
|
||||
0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06,
|
||||
0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c,
|
||||
0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17,
|
||||
0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80,
|
||||
0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80,
|
||||
0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d,
|
||||
};
|
||||
static constexpr unsigned char normal1[] = {
|
||||
0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f,
|
||||
0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e,
|
||||
0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04,
|
||||
0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09,
|
||||
0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16,
|
||||
0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f,
|
||||
0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36,
|
||||
0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33,
|
||||
0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08,
|
||||
0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e,
|
||||
0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41,
|
||||
0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03,
|
||||
0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22,
|
||||
0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04,
|
||||
0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45,
|
||||
0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03,
|
||||
0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81,
|
||||
0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75,
|
||||
0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1,
|
||||
0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a,
|
||||
0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11,
|
||||
0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09,
|
||||
0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89,
|
||||
0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6,
|
||||
0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09,
|
||||
0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50,
|
||||
0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05,
|
||||
0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83,
|
||||
0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05,
|
||||
0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80,
|
||||
0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80,
|
||||
0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07,
|
||||
0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e,
|
||||
0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07,
|
||||
0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06,
|
||||
};
|
||||
auto lower = static_cast<uint16_t>(cp);
|
||||
if (cp < 0x10000) {
|
||||
return is_printable(lower, singletons0,
|
||||
sizeof(singletons0) / sizeof(*singletons0),
|
||||
singletons0_lower, normal0, sizeof(normal0));
|
||||
}
|
||||
if (cp < 0x20000) {
|
||||
return is_printable(lower, singletons1,
|
||||
sizeof(singletons1) / sizeof(*singletons1),
|
||||
singletons1_lower, normal1, sizeof(normal1));
|
||||
}
|
||||
if (0x2a6de <= cp && cp < 0x2a700) return false;
|
||||
if (0x2b735 <= cp && cp < 0x2b740) return false;
|
||||
if (0x2b81e <= cp && cp < 0x2b820) return false;
|
||||
if (0x2cea2 <= cp && cp < 0x2ceb0) return false;
|
||||
if (0x2ebe1 <= cp && cp < 0x2f800) return false;
|
||||
if (0x2fa1e <= cp && cp < 0x30000) return false;
|
||||
if (0x3134b <= cp && cp < 0xe0100) return false;
|
||||
if (0xe01f0 <= cp && cp < 0x110000) return false;
|
||||
return cp < 0x110000;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
||||
return add_space ? " '{}'" : "'{}'";
|
||||
inline auto needs_escape(uint32_t cp) -> bool {
|
||||
return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' ||
|
||||
!is_printable(cp);
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
|
||||
template <typename Char> struct find_escape_result {
|
||||
const Char* begin;
|
||||
const Char* end;
|
||||
uint32_t cp;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
auto find_escape(const Char* begin, const Char* end)
|
||||
-> find_escape_result<Char> {
|
||||
for (; begin != end; ++begin) {
|
||||
auto cp = static_cast<typename std::make_unsigned<Char>::type>(*begin);
|
||||
if (sizeof(Char) == 1 && cp >= 0x80) continue;
|
||||
if (needs_escape(cp)) return {begin, begin + 1, cp};
|
||||
}
|
||||
return {begin, nullptr, 0};
|
||||
}
|
||||
|
||||
inline auto find_escape(const char* begin, const char* end)
|
||||
-> find_escape_result<char> {
|
||||
if (!is_utf8()) return find_escape<char>(begin, end);
|
||||
auto result = find_escape_result<char>{end, nullptr, 0};
|
||||
for_each_codepoint(string_view(begin, to_unsigned(end - begin)),
|
||||
[&](uint32_t cp, string_view sv) {
|
||||
if (needs_escape(cp)) {
|
||||
result = {sv.begin(), sv.end(), cp};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
|
||||
*out++ = '"';
|
||||
auto begin = str.begin(), end = str.end();
|
||||
do {
|
||||
auto escape = find_escape(begin, end);
|
||||
out = copy_str<Char>(begin, escape.begin, out);
|
||||
begin = escape.end;
|
||||
if (!begin) break;
|
||||
auto c = static_cast<Char>(escape.cp);
|
||||
switch (escape.cp) {
|
||||
case '\n':
|
||||
*out++ = '\\';
|
||||
c = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
*out++ = '\\';
|
||||
c = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
*out++ = '\\';
|
||||
c = 't';
|
||||
break;
|
||||
case '"':
|
||||
FMT_FALLTHROUGH;
|
||||
case '\\':
|
||||
*out++ = '\\';
|
||||
break;
|
||||
default:
|
||||
if (is_utf8()) {
|
||||
if (escape.cp < 0x100) {
|
||||
out = format_to(out, "\\x{:02x}", escape.cp);
|
||||
continue;
|
||||
}
|
||||
if (escape.cp < 0x10000) {
|
||||
out = format_to(out, "\\u{:04x}", escape.cp);
|
||||
continue;
|
||||
}
|
||||
if (escape.cp < 0x110000) {
|
||||
out = format_to(out, "\\U{:08x}", escape.cp);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (Char escape_char : basic_string_view<Char>(
|
||||
escape.begin, to_unsigned(escape.end - escape.begin))) {
|
||||
out = format_to(
|
||||
out, "\\x{:02x}",
|
||||
static_cast<typename std::make_unsigned<Char>::type>(escape_char));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
*out++ = c;
|
||||
} while (begin != end);
|
||||
*out++ = '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
|
||||
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
|
||||
auto sv = std_string_view<Char>(str);
|
||||
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||
*out++ = '\'';
|
||||
*out++ = v;
|
||||
*out++ = '\'';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||
!std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
@ -193,148 +547,205 @@ template <typename T> struct is_tuple_like {
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
// C++11 generic lambda for format().
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
out = detail::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
detail::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(
|
||||
std::declval<FormatContext>().out())>::type out;
|
||||
int i;
|
||||
typename FormatContext::iterator& out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
size_t i = 0;
|
||||
detail::copy(formatting.prefix, out);
|
||||
|
||||
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
detail::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const RangeT& values,
|
||||
FormatContext& ctx) {
|
||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||
size_t i = 0;
|
||||
auto it = values.begin();
|
||||
auto end = values.end();
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
out = detail::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
detail::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
out = format_to(out, " ... <other elements>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
return detail::copy(formatting.postfix, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple{t}, sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
*out++ = '(';
|
||||
detail::for_each(values, format_each<FormatContext>{0, out});
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||
!detail::is_map<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
fmt::is_range<T, Char>::value
|
||||
// Workaround a bug in MSVC 2019 and earlier.
|
||||
#if !FMT_MSC_VER
|
||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <
|
||||
typename FormatContext, typename U,
|
||||
FMT_ENABLE_IF(
|
||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
|
||||
const T, T>>::value)>
|
||||
auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||
Char prefix = '{';
|
||||
Char postfix = '}';
|
||||
#else
|
||||
Char prefix = detail::is_set<T>::value ? '{' : '[';
|
||||
Char postfix = detail::is_set<T>::value ? '}' : ']';
|
||||
#endif
|
||||
auto out = ctx.out();
|
||||
*out++ = prefix;
|
||||
int i = 0;
|
||||
auto it = std::begin(range);
|
||||
auto end = std::end(range);
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, *it);
|
||||
++i;
|
||||
}
|
||||
*out++ = postfix;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
detail::is_map<T>::value
|
||||
// Workaround a bug in MSVC 2019 and earlier.
|
||||
#if !FMT_MSC_VER
|
||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <
|
||||
typename FormatContext, typename U,
|
||||
FMT_ENABLE_IF(
|
||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
|
||||
const T, T>>::value)>
|
||||
auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
*out++ = '{';
|
||||
int i = 0;
|
||||
for (const auto& item : map) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, item.first);
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
out = detail::write_range_entry<Char>(out, item.second);
|
||||
++i;
|
||||
}
|
||||
*out++ = '}';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
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} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||
|
||||
// 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>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||
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:
|
||||
template <typename FormatContext, size_t... N>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
detail::index_sequence<N...>) {
|
||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||
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)
|
||||
FMT_THROW(format_error("incompatible format specs for tuple elements"));
|
||||
}
|
||||
#endif
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
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, typename Arg, typename... Args>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = ctx.out();
|
||||
out = base{}.format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
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) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
@ -347,14 +758,15 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
||||
string_view sep) {
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||
-> tuple_join_view<char, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
wstring_view sep) {
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
|
||||
basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<wchar_t, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
@ -370,17 +782,12 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
|
||||
string_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
|
||||
wstring_view sep) {
|
||||
auto join(std::initializer_list<T> list, string_view sep)
|
||||
-> join_view<const T*, const T*> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
236
include/fmt/xchar.h
Normal file
236
include/fmt/xchar.h
Normal file
@ -0,0 +1,236 @@
|
||||
// Formatting library for C++ - optional wchar_t and exotic character support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_XCHAR_H_
|
||||
#define FMT_XCHAR_H_
|
||||
|
||||
#include <cwchar>
|
||||
#include <tuple>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
using wstring_view = basic_string_view<wchar_t>;
|
||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||
using wformat_context = buffer_context<wchar_t>;
|
||||
using wformat_args = basic_format_args<wformat_context>;
|
||||
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround broken conversion on older gcc.
|
||||
template <typename... Args> using wformat_string = wstring_view;
|
||||
#else
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
#endif
|
||||
|
||||
template <> struct is_char<wchar_t> : std::true_type {};
|
||||
template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||
template <> struct is_char<char16_t> : std::true_type {};
|
||||
template <> struct is_char<char32_t> : std::true_type {};
|
||||
|
||||
template <typename... Args>
|
||||
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
inline namespace literals {
|
||||
constexpr auto operator"" _format(const wchar_t* s, size_t n)
|
||||
-> detail::udl_formatter<wchar_t> {
|
||||
return {{s, n}};
|
||||
}
|
||||
|
||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
|
||||
return {s};
|
||||
}
|
||||
#endif
|
||||
} // namespace literals
|
||||
|
||||
template <typename It, typename Sentinel>
|
||||
auto join(It begin, Sentinel end, wstring_view sep)
|
||||
-> join_view<It, Sentinel, wchar_t> {
|
||||
return {begin, end, sep};
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
auto join(Range&& range, wstring_view sep)
|
||||
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
||||
wchar_t> {
|
||||
return join(std::begin(range), std::end(range), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, wstring_view sep)
|
||||
-> join_view<const T*, const T*, wchar_t> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto vformat(basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat(to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat(
|
||||
const Locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
auto vformat_to(OutputIt out, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
return vformat_to(out, to_string_view(fmt), vargs);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char, size_t SIZE,
|
||||
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
|
||||
const S& format_str, Args&&... args) ->
|
||||
typename buffer_context<Char>::iterator {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
detail::vformat_to(buf, to_string_view(format_str), vargs, {});
|
||||
return detail::buffer_appender<Char>(buf);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to(
|
||||
OutputIt out, const Locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat_to(out, loc, to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to_n(
|
||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||
n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
return vformat_to_n(out, n, to_string_view(fmt), vargs);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||
detail::counting_buffer<Char> buf;
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
detail::vformat_to(buf, to_string_view(fmt), vargs);
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
buffer.push_back(L'\0');
|
||||
if (std::fputws(buffer.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
inline void vprint(wstring_view fmt, wformat_args args) {
|
||||
vprint(stdout, fmt, args);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||
*/
|
||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||
return format(FMT_STRING(L"{}"), value);
|
||||
}
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_XCHAR_H_
|
99
src/fmt.cc
Normal file
99
src/fmt.cc
Normal file
@ -0,0 +1,99 @@
|
||||
module;
|
||||
#ifndef __cpp_modules
|
||||
# error Module not supported.
|
||||
#endif
|
||||
|
||||
// put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <clocale>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <cwchar>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if _MSC_VER
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
#if __has_include(<winapifamily.h>)
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# include <io.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
export module fmt;
|
||||
|
||||
#define FMT_MODULE_EXPORT export
|
||||
#define FMT_MODULE_EXPORT_BEGIN export {
|
||||
#define FMT_MODULE_EXPORT_END }
|
||||
#define FMT_BEGIN_DETAIL_NAMESPACE \
|
||||
} \
|
||||
namespace detail {
|
||||
#define FMT_END_DETAIL_NAMESPACE \
|
||||
} \
|
||||
export {
|
||||
// all library-provided declarations and definitions
|
||||
// must be in the module purview to be exported
|
||||
#include "fmt/args.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/compile.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/os.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
// gcc doesn't yet implement private module fragments
|
||||
#if !FMT_GCC_VERSION
|
||||
module : private;
|
||||
#endif
|
||||
|
||||
#include "format.cc"
|
||||
#include "os.cc"
|
@ -10,6 +10,52 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// DEPRECATED!
|
||||
template <typename T = void> struct basic_data {
|
||||
FMT_API static constexpr const char digits[100][2] = {
|
||||
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
|
||||
{'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
|
||||
{'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
|
||||
{'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
|
||||
{'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
|
||||
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
|
||||
{'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
|
||||
{'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
|
||||
{'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
|
||||
{'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
|
||||
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
|
||||
{'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
|
||||
{'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
|
||||
{'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
|
||||
{'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
|
||||
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
|
||||
{'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
|
||||
FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
|
||||
FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
|
||||
FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
|
||||
0};
|
||||
FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
|
||||
0};
|
||||
FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
|
||||
0x1000000u | ' '};
|
||||
};
|
||||
|
||||
#ifdef FMT_SHARED
|
||||
// Required for -flto, -fivisibility=hidden and -shared to work
|
||||
extern template struct basic_data<void>;
|
||||
#endif
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
// DEPRECATED! These are here only for ABI compatiblity.
|
||||
template <typename T> constexpr const char basic_data<T>::digits[][2];
|
||||
template <typename T> constexpr const char basic_data<T>::hex_digits[];
|
||||
template <typename T> constexpr const char basic_data<T>::signs[];
|
||||
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
|
||||
template <typename T>
|
||||
constexpr const char basic_data<T>::right_padding_shifts[];
|
||||
template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
@ -23,9 +69,12 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
|
||||
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
|
||||
FMT_NOEXCEPT;
|
||||
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
|
||||
FMT_NOEXCEPT;
|
||||
} // namespace detail
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, detail::float_specs,
|
||||
@ -38,15 +87,18 @@ template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
|
||||
template FMT_API char detail::thousands_sep_impl(locale_ref);
|
||||
template FMT_API auto detail::thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<char>;
|
||||
template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
|
||||
// DEPRECATED!
|
||||
// There is no correspondent extern template in format.h because of
|
||||
// incompatibility between clang and gcc (#2377).
|
||||
template FMT_API void detail::vformat_to(
|
||||
detail::buffer<char>&, string_view,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||
|
||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
@ -60,10 +112,13 @@ template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
|
||||
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
|
||||
template FMT_API auto detail::thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<wchar_t>;
|
||||
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template struct detail::basic_data<void>;
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
144
src/os.cc
144
src/os.cc
@ -25,21 +25,18 @@
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# ifndef S_IRGRP
|
||||
# define S_IRGRP 0
|
||||
# endif
|
||||
# ifndef S_IROTH
|
||||
# define S_IROTH 0
|
||||
# endif
|
||||
# endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
@ -55,16 +52,16 @@
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
using RWResult = int;
|
||||
using rwresult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#else
|
||||
#elif FMT_USE_FCNTL
|
||||
// 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; }
|
||||
#endif
|
||||
@ -73,14 +70,14 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
|
||||
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
int detail::utf16_to_utf8::convert(wstring_view s) {
|
||||
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
@ -101,45 +98,85 @@ int detail::utf16_to_utf8::convert(wstring_view s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void windows_error::init(int err_code, string_view format_str,
|
||||
format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
detail::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error& base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
namespace detail {
|
||||
|
||||
class system_message {
|
||||
system_message(const system_message&) = delete;
|
||||
void operator=(const system_message&) = delete;
|
||||
|
||||
unsigned long result_;
|
||||
wchar_t* message_;
|
||||
|
||||
static bool is_whitespace(wchar_t c) FMT_NOEXCEPT {
|
||||
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
|
||||
}
|
||||
|
||||
public:
|
||||
explicit system_message(unsigned long error_code)
|
||||
: result_(0), message_(nullptr) {
|
||||
result_ = FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
|
||||
if (result_ != 0) {
|
||||
while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
|
||||
--result_;
|
||||
}
|
||||
}
|
||||
}
|
||||
~system_message() { LocalFree(message_); }
|
||||
explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; }
|
||||
operator basic_string_view<wchar_t>() const FMT_NOEXCEPT {
|
||||
return basic_string_view<wchar_t>(message_, result_);
|
||||
}
|
||||
};
|
||||
|
||||
class utf8_system_category final : public std::error_category {
|
||||
public:
|
||||
const char* name() const FMT_NOEXCEPT override { return "system"; }
|
||||
std::string message(int error_code) const override {
|
||||
system_message msg(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
return utf8_message.str();
|
||||
}
|
||||
}
|
||||
return "unknown error";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_API const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
static const detail::utf8_system_category category;
|
||||
return category;
|
||||
}
|
||||
|
||||
std::system_error vwindows_error(int err_code, string_view format_str,
|
||||
format_args args) {
|
||||
auto ec = std::error_code(err_code, system_category());
|
||||
return std::system_error(ec, vformat(format_str, args));
|
||||
}
|
||||
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
const char* message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
wmemory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
for (;;) {
|
||||
wchar_t* system_message = &buf[0];
|
||||
int result = FormatMessageW(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
|
||||
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message,
|
||||
static_cast<uint32_t>(buf.size()), nullptr);
|
||||
if (result != 0) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
system_message msg(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
|
||||
return;
|
||||
}
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
}
|
||||
FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
void report_windows_error(int error_code,
|
||||
fmt::string_view message) FMT_NOEXCEPT {
|
||||
void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT {
|
||||
report_error(detail::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
@ -174,7 +211,10 @@ int buffered_file::fileno() const {
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
# ifdef _WIN32
|
||||
using mode_t = int;
|
||||
# endif
|
||||
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
@ -228,14 +268,14 @@ long long file::size() const {
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
return detail::to_unsigned(result);
|
||||
@ -259,10 +299,10 @@ void file::dup2(int fd) {
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT {
|
||||
void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) ec = error_code(errno);
|
||||
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
||||
}
|
||||
|
||||
void file::pipe(file& read_end, file& write_end) {
|
||||
@ -288,12 +328,12 @@ void file::pipe(file& read_end, file& write_end) {
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
#if defined(__MINGW32__) && defined(_POSIX_)
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
#else
|
||||
# else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
#endif
|
||||
# endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
@ -313,5 +353,9 @@ long getpagesize() {
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
|
||||
FMT_API void ostream::grow(size_t) {
|
||||
if (this->size() == this->capacity()) flush();
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
||||
|
@ -2,5 +2,3 @@ This directory contains build support files such as
|
||||
|
||||
* CMake modules
|
||||
* Build scripts
|
||||
* qmake (static build with dynamic libc only)
|
||||
|
||||
|
@ -6,11 +6,8 @@ clone_depth: 1
|
||||
|
||||
image:
|
||||
- Visual Studio 2015
|
||||
- Visual Studio 2019
|
||||
- Visual Studio 2017
|
||||
|
||||
platform:
|
||||
- Win32
|
||||
- x64
|
||||
|
||||
environment:
|
||||
@ -18,13 +15,6 @@ environment:
|
||||
MSVC_DEFAULT_OPTIONS: ON
|
||||
BUILD: msvc
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- image: Visual Studio 2015
|
||||
platform: Win32
|
||||
- image: Visual Studio 2019
|
||||
platform: Win32
|
||||
|
||||
before_build:
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
1
support/bazel/.bazelrc
Normal file
1
support/bazel/.bazelrc
Normal file
@ -0,0 +1 @@
|
||||
build --symlink_prefix=/ # Out of source build
|
1
support/bazel/.bazelversion
Normal file
1
support/bazel/.bazelversion
Normal file
@ -0,0 +1 @@
|
||||
4.2.1
|
29
support/bazel/BUILD.bazel
Normal file
29
support/bazel/BUILD.bazel
Normal file
@ -0,0 +1,29 @@
|
||||
cc_library(
|
||||
name = "fmt",
|
||||
srcs = [
|
||||
#"src/fmt.cc", # No C++ module support
|
||||
"src/format.cc",
|
||||
"src/os.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"include/fmt/args.h",
|
||||
"include/fmt/chrono.h",
|
||||
"include/fmt/color.h",
|
||||
"include/fmt/compile.h",
|
||||
"include/fmt/core.h",
|
||||
"include/fmt/format.h",
|
||||
"include/fmt/format-inl.h",
|
||||
"include/fmt/locale.h",
|
||||
"include/fmt/os.h",
|
||||
"include/fmt/ostream.h",
|
||||
"include/fmt/printf.h",
|
||||
"include/fmt/ranges.h",
|
||||
"include/fmt/xchar.h",
|
||||
],
|
||||
includes = [
|
||||
"include",
|
||||
"src",
|
||||
],
|
||||
strip_include_prefix = "include",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
73
support/bazel/README.md
Normal file
73
support/bazel/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Bazel support
|
||||
|
||||
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, 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
|
||||
|
||||
The following minimal example shows how to use {fmt} as a dependency within a Bazel project.
|
||||
|
||||
The following file structure is assumed:
|
||||
|
||||
```
|
||||
example
|
||||
├── BUILD.bazel
|
||||
├── main.cpp
|
||||
└── WORKSPACE.bazel
|
||||
```
|
||||
|
||||
*main.cpp*:
|
||||
|
||||
```c++
|
||||
#include "fmt/core.h"
|
||||
|
||||
int main() {
|
||||
fmt::print("The answer is {}\n", 42);
|
||||
}
|
||||
```
|
||||
|
||||
The expected output of this example is `The answer is 42`.
|
||||
|
||||
*WORKSPACE.bazel*:
|
||||
|
||||
```python
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "fmt",
|
||||
branch = "master",
|
||||
remote = "https://github.com/fmtlib/fmt",
|
||||
patch_cmds = [
|
||||
"mv support/bazel/.bazelrc .bazelrc",
|
||||
"mv support/bazel/.bazelversion .bazelversion",
|
||||
"mv support/bazel/BUILD.bazel BUILD.bazel",
|
||||
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
|
||||
],
|
||||
# Windows-related patch commands are only needed in the case MSYS2 is not installed.
|
||||
# More details about the installation process of MSYS2 on Windows systems can be found here:
|
||||
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
|
||||
# Even if MSYS2 is installed the Windows related patch commands can still be used.
|
||||
patch_cmds_win = [
|
||||
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
|
||||
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
|
||||
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
|
||||
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
|
||||
|
||||
*BUILD.bazel*:
|
||||
|
||||
```python
|
||||
cc_binary(
|
||||
name = "Demo",
|
||||
srcs = ["main.cpp"],
|
||||
deps = ["@fmt"],
|
||||
)
|
||||
```
|
||||
|
||||
The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}.
|
||||
|
||||
To execute the binary you can run `bazel run //:Demo`.
|
||||
|
1
support/bazel/WORKSPACE.bazel
Normal file
1
support/bazel/WORKSPACE.bazel
Normal file
@ -0,0 +1 @@
|
||||
workspace(name = "fmt")
|
58
support/build-docs.py
Executable file
58
support/build-docs.py
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
# Build the documentation in CI.
|
||||
|
||||
from __future__ import print_function
|
||||
import errno, os, shutil, subprocess, sys, urllib
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
|
||||
def rmtree_if_exists(dir):
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
|
||||
# Build the docs.
|
||||
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
||||
import build
|
||||
build.create_build_env()
|
||||
html_dir = build.build_docs()
|
||||
|
||||
repo = 'fmtlib.github.io'
|
||||
branch = os.environ['GITHUB_REF']
|
||||
is_ci = 'CI' in os.environ
|
||||
if is_ci and branch != 'refs/heads/master':
|
||||
print('Branch: ' + branch)
|
||||
exit(0) # Ignore non-master branches
|
||||
if is_ci and 'KEY' not in os.environ:
|
||||
# Don't update the repo if building in CI from an account that doesn't have
|
||||
# push access.
|
||||
print('Skipping update of ' + repo)
|
||||
exit(0)
|
||||
|
||||
# Clone the fmtlib.github.io repo.
|
||||
rmtree_if_exists(repo)
|
||||
git_url = 'https://github.com/' if is_ci else 'git@github.com:'
|
||||
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
||||
|
||||
# Copy docs to the repo.
|
||||
target_dir = os.path.join(repo, 'dev')
|
||||
rmtree_if_exists(target_dir)
|
||||
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
||||
if is_ci:
|
||||
check_call(['git', 'config', '--global', 'user.name', 'fmtbot'])
|
||||
check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev'])
|
||||
|
||||
# Push docs to GitHub pages.
|
||||
check_call(['git', 'add', '--all'], cwd=repo)
|
||||
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
||||
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
||||
cmd = 'git push'
|
||||
if is_ci:
|
||||
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
||||
# Print the output without the key.
|
||||
print(p.communicate()[0].decode('utf-8').replace(os.environ['KEY'], '$KEY'))
|
||||
if p.returncode != 0:
|
||||
raise subprocess.CalledProcessError(p.returncode, cmd)
|
@ -1,3 +1,4 @@
|
||||
import java.nio.file.Paths
|
||||
|
||||
// General gradle arguments for root project
|
||||
buildscript {
|
||||
@ -7,24 +8,25 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
//
|
||||
// https://developer.android.com/studio/releases/gradle-plugin
|
||||
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
||||
//
|
||||
// Notice that 3.3.0 here is the version of [Android Gradle Plugin]
|
||||
// Accroding to URL above you will need Gradle 5.0 or higher
|
||||
// Notice that 4.0.0 here is the version of [Android Gradle Plugin]
|
||||
// Accroding to URL above you will need Gradle 6.1 or higher
|
||||
//
|
||||
// If you are using Android Studio, and it is using Gradle's lower
|
||||
// version, Use the plugin version 3.1.3 ~ 3.2.0 for Gradle 4.4 ~ 4.10
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
classpath "com.android.tools.build:gradle:4.1.1"
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
// Output: Shared library (.so) for Android
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
// Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir
|
||||
def rootDir = Paths.get(project.buildDir.getParent()).getParent()
|
||||
println("rootDir: ${rootDir}")
|
||||
|
||||
// Output: Shared library (.so) for Android
|
||||
apply plugin: "com.android.library"
|
||||
android {
|
||||
compileSdkVersion 25 // Android 7.0
|
||||
|
||||
@ -41,13 +43,13 @@ android {
|
||||
include "arm64-v8a", "armeabi-v7a", "x86_64"
|
||||
}
|
||||
}
|
||||
ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21 // Android 5.0+
|
||||
targetSdkVersion 25 // Follow Compile SDK
|
||||
versionCode 21 // Follow release count
|
||||
versionName "5.3.0" // Follow Official version
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
versionCode 34 // Follow release count
|
||||
versionName "7.1.2" // Follow Official version
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@ -56,9 +58,9 @@ android {
|
||||
arguments "-DFMT_TEST=false" // Skip test
|
||||
arguments "-DFMT_DOC=false" // Skip document
|
||||
cppFlags "-std=c++17"
|
||||
targets "fmt"
|
||||
}
|
||||
}
|
||||
println("Gradle CMake Plugin: ")
|
||||
println(externalNativeBuild.cmake.cppFlags)
|
||||
println(externalNativeBuild.cmake.arguments)
|
||||
}
|
||||
@ -69,16 +71,27 @@ android {
|
||||
// neighbor of the top level cmake
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "../CMakeLists.txt"
|
||||
version "3.10.0+"
|
||||
path "${rootDir}/CMakeLists.txt"
|
||||
// buildStagingDirectory "./build" // Custom path for cmake output
|
||||
}
|
||||
//println(cmake.path)
|
||||
}
|
||||
|
||||
sourceSets{
|
||||
// Android Manifest for Gradle
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
manifest.srcFile "AndroidManifest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.android.com/studio/build/native-dependencies#build_system_configuration
|
||||
buildFeatures {
|
||||
prefab true
|
||||
prefabPublishing true
|
||||
}
|
||||
prefab {
|
||||
fmt {
|
||||
headers "${rootDir}/include"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,20 +101,32 @@ assemble.doLast
|
||||
// Instead of `ninja install`, Gradle will deploy the files.
|
||||
// We are doing this since FMT is dependent to the ANDROID_STL after build
|
||||
copy {
|
||||
from 'build/intermediates/cmake'
|
||||
into '../libs'
|
||||
from "build/intermediates/cmake"
|
||||
into "${rootDir}/libs"
|
||||
}
|
||||
// Copy debug binaries
|
||||
copy {
|
||||
from '../libs/debug/obj'
|
||||
into '../libs/debug'
|
||||
from "${rootDir}/libs/debug/obj"
|
||||
into "${rootDir}/libs/debug"
|
||||
}
|
||||
// Copy Release binaries
|
||||
copy {
|
||||
from '../libs/release/obj'
|
||||
into '../libs/release'
|
||||
from "${rootDir}/libs/release/obj"
|
||||
into "${rootDir}/libs/release"
|
||||
}
|
||||
// Remove empty directory
|
||||
delete '../libs/debug/obj'
|
||||
delete '../libs/release/obj'
|
||||
delete "${rootDir}/libs/debug/obj"
|
||||
delete "${rootDir}/libs/release/obj"
|
||||
|
||||
// Copy AAR files. Notice that the aar is named after the folder of this script.
|
||||
copy {
|
||||
from "build/outputs/aar/support-release.aar"
|
||||
into "${rootDir}/libs"
|
||||
rename "support-release.aar", "fmt-release.aar"
|
||||
}
|
||||
copy {
|
||||
from "build/outputs/aar/support-debug.aar"
|
||||
into "${rootDir}/libs"
|
||||
rename "support-debug.aar", "fmt-debug.aar"
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
# Staticlib configuration for qmake builds
|
||||
# For some reason qmake 3.1 fails to identify source dependencies and excludes format.cc and printf.cc
|
||||
# from compilation so it _MUST_ be called as qmake -nodepend
|
||||
# A workaround is implemented below: a custom compiler is defined which does not track dependencies
|
||||
|
||||
TEMPLATE = lib
|
||||
|
||||
TARGET = fmt
|
||||
|
||||
QMAKE_EXT_CPP = .cc
|
||||
|
||||
CONFIG = staticlib warn_on c++11
|
||||
|
||||
FMT_SOURCES = \
|
||||
../src/format.cc \
|
||||
../src/posix.cc
|
||||
|
||||
fmt.name = libfmt
|
||||
fmt.input = FMT_SOURCES
|
||||
fmt.output = ${QMAKE_FILE_BASE}$$QMAKE_EXT_OBJ
|
||||
fmt.clean = ${QMAKE_FILE_BASE}$$QMAKE_EXT_OBJ
|
||||
fmt.depends = ${QMAKE_FILE_IN}
|
||||
# QMAKE_RUN_CXX will not be expanded
|
||||
fmt.commands = $$QMAKE_CXX -c $$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_WARN_ON $$QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO $$QMAKE_CXXFLAGS_CXX11 ${QMAKE_FILE_IN}
|
||||
fmt.variable_out = OBJECTS
|
||||
fmt.CONFIG = no_dependencies no_link
|
||||
QMAKE_EXTRA_COMPILERS += fmt
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Manage site and releases.
|
||||
|
||||
@ -163,6 +163,13 @@ def update_site(env):
|
||||
if version.startswith('7.'):
|
||||
b.data = b.data.replace(', std::size_t', ', size_t')
|
||||
b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
|
||||
if version.startswith('7.1.'):
|
||||
b.data = b.data.replace(', std::size_t', ', size_t')
|
||||
b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
|
||||
b.data = b.data.replace(
|
||||
'fmt::format_to(OutputIt, const S&, Args&&...)',
|
||||
'fmt::format_to(OutputIt, const S&, Args&&...) -> ' +
|
||||
'typename std::enable_if<enable, OutputIt>::type')
|
||||
b.data = b.data.replace('aa long', 'a long')
|
||||
b.data = b.data.replace('serveral', 'several')
|
||||
if version.startswith('6.2.'):
|
||||
@ -233,7 +240,7 @@ def release(args):
|
||||
# Update the version in the changelog.
|
||||
title_len = 0
|
||||
for line in fileinput.input(changelog_path, inplace=True):
|
||||
if line.decode('utf-8').startswith(version + ' - TBD'):
|
||||
if line.startswith(version + ' - TBD'):
|
||||
line = version + ' - ' + datetime.date.today().isoformat()
|
||||
title_len = len(line)
|
||||
line += '\n'
|
||||
|
201
support/printable.py
Executable file
201
support/printable.py
Executable file
@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script is based on
|
||||
# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py
|
||||
# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT.
|
||||
|
||||
# This script uses the following Unicode tables:
|
||||
# - UnicodeData.txt
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
import csv
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
NUM_CODEPOINTS=0x110000
|
||||
|
||||
def to_ranges(iter):
|
||||
current = None
|
||||
for i in iter:
|
||||
if current is None or i != current[1] or i in (0x10000, 0x20000):
|
||||
if current is not None:
|
||||
yield tuple(current)
|
||||
current = [i, i + 1]
|
||||
else:
|
||||
current[1] += 1
|
||||
if current is not None:
|
||||
yield tuple(current)
|
||||
|
||||
def get_escaped(codepoints):
|
||||
for c in codepoints:
|
||||
if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '):
|
||||
yield c.value
|
||||
|
||||
def get_file(f):
|
||||
try:
|
||||
return open(os.path.basename(f))
|
||||
except FileNotFoundError:
|
||||
subprocess.run(["curl", "-O", f], check=True)
|
||||
return open(os.path.basename(f))
|
||||
|
||||
Codepoint = namedtuple('Codepoint', 'value class_')
|
||||
|
||||
def get_codepoints(f):
|
||||
r = csv.reader(f, delimiter=";")
|
||||
prev_codepoint = 0
|
||||
class_first = None
|
||||
for row in r:
|
||||
codepoint = int(row[0], 16)
|
||||
name = row[1]
|
||||
class_ = row[2]
|
||||
|
||||
if class_first is not None:
|
||||
if not name.endswith("Last>"):
|
||||
raise ValueError("Missing Last after First")
|
||||
|
||||
for c in range(prev_codepoint + 1, codepoint):
|
||||
yield Codepoint(c, class_first)
|
||||
|
||||
class_first = None
|
||||
if name.endswith("First>"):
|
||||
class_first = class_
|
||||
|
||||
yield Codepoint(codepoint, class_)
|
||||
prev_codepoint = codepoint
|
||||
|
||||
if class_first is not None:
|
||||
raise ValueError("Missing Last after First")
|
||||
|
||||
for c in range(prev_codepoint + 1, NUM_CODEPOINTS):
|
||||
yield Codepoint(c, None)
|
||||
|
||||
def compress_singletons(singletons):
|
||||
uppers = [] # (upper, # items in lowers)
|
||||
lowers = []
|
||||
|
||||
for i in singletons:
|
||||
upper = i >> 8
|
||||
lower = i & 0xff
|
||||
if len(uppers) == 0 or uppers[-1][0] != upper:
|
||||
uppers.append((upper, 1))
|
||||
else:
|
||||
upper, count = uppers[-1]
|
||||
uppers[-1] = upper, count + 1
|
||||
lowers.append(lower)
|
||||
|
||||
return uppers, lowers
|
||||
|
||||
def compress_normal(normal):
|
||||
# lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f
|
||||
# lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff
|
||||
compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)]
|
||||
|
||||
prev_start = 0
|
||||
for start, count in normal:
|
||||
truelen = start - prev_start
|
||||
falselen = count
|
||||
prev_start = start + count
|
||||
|
||||
assert truelen < 0x8000 and falselen < 0x8000
|
||||
entry = []
|
||||
if truelen > 0x7f:
|
||||
entry.append(0x80 | (truelen >> 8))
|
||||
entry.append(truelen & 0xff)
|
||||
else:
|
||||
entry.append(truelen & 0x7f)
|
||||
if falselen > 0x7f:
|
||||
entry.append(0x80 | (falselen >> 8))
|
||||
entry.append(falselen & 0xff)
|
||||
else:
|
||||
entry.append(falselen & 0x7f)
|
||||
|
||||
compressed.append(entry)
|
||||
|
||||
return compressed
|
||||
|
||||
def print_singletons(uppers, lowers, uppersname, lowersname):
|
||||
print(" static constexpr singleton {}[] = {{".format(uppersname))
|
||||
for u, c in uppers:
|
||||
print(" {{{:#04x}, {}}},".format(u, c))
|
||||
print(" };")
|
||||
print(" static constexpr unsigned char {}[] = {{".format(lowersname))
|
||||
for i in range(0, len(lowers), 8):
|
||||
print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8])))
|
||||
print(" };")
|
||||
|
||||
def print_normal(normal, normalname):
|
||||
print(" static constexpr unsigned char {}[] = {{".format(normalname))
|
||||
for v in normal:
|
||||
print(" {}".format(" ".join("{:#04x},".format(i) for i in v)))
|
||||
print(" };")
|
||||
|
||||
def main():
|
||||
file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt")
|
||||
|
||||
codepoints = get_codepoints(file)
|
||||
|
||||
CUTOFF=0x10000
|
||||
singletons0 = []
|
||||
singletons1 = []
|
||||
normal0 = []
|
||||
normal1 = []
|
||||
extra = []
|
||||
|
||||
for a, b in to_ranges(get_escaped(codepoints)):
|
||||
if a > 2 * CUTOFF:
|
||||
extra.append((a, b - a))
|
||||
elif a == b - 1:
|
||||
if a & CUTOFF:
|
||||
singletons1.append(a & ~CUTOFF)
|
||||
else:
|
||||
singletons0.append(a)
|
||||
elif a == b - 2:
|
||||
if a & CUTOFF:
|
||||
singletons1.append(a & ~CUTOFF)
|
||||
singletons1.append((a + 1) & ~CUTOFF)
|
||||
else:
|
||||
singletons0.append(a)
|
||||
singletons0.append(a + 1)
|
||||
else:
|
||||
if a >= 2 * CUTOFF:
|
||||
extra.append((a, b - a))
|
||||
elif a & CUTOFF:
|
||||
normal1.append((a & ~CUTOFF, b - a))
|
||||
else:
|
||||
normal0.append((a, b - a))
|
||||
|
||||
singletons0u, singletons0l = compress_singletons(singletons0)
|
||||
singletons1u, singletons1l = compress_singletons(singletons1)
|
||||
normal0 = compress_normal(normal0)
|
||||
normal1 = compress_normal(normal1)
|
||||
|
||||
print("""\
|
||||
inline auto is_printable(uint32_t cp) -> bool {\
|
||||
""")
|
||||
print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower')
|
||||
print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower')
|
||||
print_normal(normal0, 'normal0')
|
||||
print_normal(normal1, 'normal1')
|
||||
print("""\
|
||||
auto lower = static_cast<uint16_t>(cp);
|
||||
if (cp < 0x10000) {
|
||||
return is_printable(lower, singletons0,
|
||||
sizeof(singletons0) / sizeof(*singletons0),
|
||||
singletons0_lower, normal0, sizeof(normal0));
|
||||
}
|
||||
if (cp < 0x20000) {
|
||||
return is_printable(lower, singletons1,
|
||||
sizeof(singletons1) / sizeof(*singletons1),
|
||||
singletons1_lower, normal1, sizeof(normal1));
|
||||
}\
|
||||
""")
|
||||
for a, b in extra:
|
||||
print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b))
|
||||
print("""\
|
||||
return cp < 0x{:x};
|
||||
}}\
|
||||
""".format(NUM_CODEPOINTS))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -65,7 +65,7 @@ class Translator(nodes.NodeVisitor):
|
||||
self.write('\n\n')
|
||||
|
||||
def visit_paragraph(self, node):
|
||||
pass
|
||||
self.write('\n\n')
|
||||
|
||||
def depart_paragraph(self, node):
|
||||
pass
|
||||
|
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Build the project on Travis CI.
|
||||
|
||||
from __future__ import print_function
|
||||
import errno, os, shutil, subprocess, sys, urllib
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
|
||||
def rmtree_if_exists(dir):
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
|
||||
def makedirs_if_not_exist(dir):
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def install_dependencies():
|
||||
branch = os.environ['TRAVIS_BRANCH']
|
||||
if branch != 'master':
|
||||
print('Branch: ' + branch)
|
||||
exit(0) # Ignore non-master branches
|
||||
check_call('curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key ' +
|
||||
'| sudo apt-key add -', shell=True)
|
||||
check_call('echo "deb https://deb.nodesource.com/node_0.10 precise main" ' +
|
||||
'| sudo tee /etc/apt/sources.list.d/nodesource.list', shell=True)
|
||||
check_call(['sudo', 'apt-get', 'update'])
|
||||
check_call(['sudo', 'apt-get', 'install', 'python-virtualenv', 'nodejs'])
|
||||
check_call(['sudo', 'npm', 'install', '-g', 'less@2.6.1', 'less-plugin-clean-css'])
|
||||
deb_file = 'doxygen_1.8.6-2_amd64.deb'
|
||||
urllib.urlretrieve('http://mirrors.kernel.org/ubuntu/pool/main/d/doxygen/' +
|
||||
deb_file, deb_file)
|
||||
check_call(['sudo', 'dpkg', '-i', deb_file])
|
||||
|
||||
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
build = os.environ['BUILD']
|
||||
if build == 'Doc':
|
||||
travis = 'TRAVIS' in os.environ
|
||||
if travis:
|
||||
install_dependencies()
|
||||
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
||||
import build
|
||||
build.create_build_env()
|
||||
html_dir = build.build_docs()
|
||||
repo = 'fmtlib.github.io'
|
||||
if travis and 'KEY' not in os.environ:
|
||||
# Don't update the repo if building on Travis from an account that
|
||||
# doesn't have push access.
|
||||
print('Skipping update of ' + repo)
|
||||
exit(0)
|
||||
# Clone the fmtlib.github.io repo.
|
||||
rmtree_if_exists(repo)
|
||||
git_url = 'https://github.com/' if travis else 'git@github.com:'
|
||||
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
||||
# Copy docs to the repo.
|
||||
target_dir = os.path.join(repo, 'dev')
|
||||
rmtree_if_exists(target_dir)
|
||||
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
||||
if travis:
|
||||
check_call(['git', 'config', '--global', 'user.name', 'amplbot'])
|
||||
check_call(['git', 'config', '--global', 'user.email', 'viz@ampl.com'])
|
||||
# Push docs to GitHub pages.
|
||||
check_call(['git', 'add', '--all'], cwd=repo)
|
||||
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
||||
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
||||
cmd = 'git push'
|
||||
if travis:
|
||||
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
||||
# Print the output without the key.
|
||||
print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
|
||||
if p.returncode != 0:
|
||||
raise subprocess.CalledProcessError(p.returncode, cmd)
|
||||
exit(0)
|
||||
|
||||
standard = os.environ['STANDARD']
|
||||
install_dir = os.path.join(fmt_dir, "_install")
|
||||
build_dir = os.path.join(fmt_dir, "_build")
|
||||
test_build_dir = os.path.join(fmt_dir, "_build_test")
|
||||
|
||||
# Configure the library.
|
||||
makedirs_if_not_exist(build_dir)
|
||||
cmake_flags = [
|
||||
'-DCMAKE_INSTALL_PREFIX=' + install_dir, '-DCMAKE_BUILD_TYPE=' + build,
|
||||
'-DCMAKE_CXX_STANDARD=' + standard
|
||||
]
|
||||
|
||||
# Make sure the fuzzers still compile.
|
||||
main_cmake_flags = list(cmake_flags)
|
||||
if 'ENABLE_FUZZING' in os.environ:
|
||||
main_cmake_flags += ['-DFMT_FUZZ=ON', '-DFMT_FUZZ_LINKMAIN=On']
|
||||
|
||||
check_call(['cmake', '-DFMT_DOC=OFF', '-DFMT_PEDANTIC=ON', '-DFMT_WERROR=ON', fmt_dir] +
|
||||
main_cmake_flags, cwd=build_dir)
|
||||
|
||||
# Build the library.
|
||||
check_call(['cmake', '--build','.'], cwd=build_dir)
|
||||
|
||||
# Test the library.
|
||||
env = os.environ.copy()
|
||||
env['CTEST_OUTPUT_ON_FAILURE'] = '1'
|
||||
if call(['make', 'test'], env=env, cwd=build_dir):
|
||||
with open(os.path.join(build_dir, 'Testing', 'Temporary', 'LastTest.log'), 'r') as f:
|
||||
print(f.read())
|
||||
sys.exit(-1)
|
||||
|
||||
# Install the library.
|
||||
check_call(['make', 'install'], cwd=build_dir)
|
||||
|
||||
# Test installation.
|
||||
makedirs_if_not_exist(test_build_dir)
|
||||
check_call(['cmake', os.path.join(fmt_dir, "test", "find-package-test")] +
|
||||
cmake_flags, cwd=test_build_dir)
|
||||
check_call(['make', '-j4'], cwd=test_build_dir)
|
@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Update the coverity branch from the master branch.
|
||||
# It is not done automatically because Coverity Scan limits
|
||||
# the number of submissions per day.
|
||||
|
||||
from __future__ import print_function
|
||||
import shutil, tempfile
|
||||
from subprocess import check_output, STDOUT
|
||||
|
||||
class Git:
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
|
||||
def __call__(self, *args):
|
||||
output = check_output(['git'] + list(args), cwd=self.dir, stderr=STDOUT)
|
||||
print(output)
|
||||
return output
|
||||
|
||||
dir = tempfile.mkdtemp()
|
||||
try:
|
||||
git = Git(dir)
|
||||
git('clone', '-b', 'coverity', 'git@github.com:fmtlib/fmt.git', dir)
|
||||
output = git('merge', '-X', 'theirs', '--no-commit', 'origin/master')
|
||||
if 'Fast-forward' not in output:
|
||||
git('reset', 'HEAD', '.travis.yml')
|
||||
git('checkout', '--', '.travis.yml')
|
||||
git('commit', '-m', 'Update coverity branch')
|
||||
git('push')
|
||||
finally:
|
||||
shutil.rmtree(dir)
|
@ -1,80 +1,50 @@
|
||||
#------------------------------------------------------------------------------
|
||||
# Build the google test library
|
||||
|
||||
# We compile Google Test ourselves instead of using pre-compiled libraries.
|
||||
# See the Google Test FAQ "Why is it not recommended to install a
|
||||
# pre-compiled copy of Google Test (for example, into /usr/local)?"
|
||||
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
|
||||
add_library(gmock STATIC
|
||||
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
|
||||
target_compile_definitions(gmock PUBLIC GTEST_HAS_STD_WSTRING=1)
|
||||
target_include_directories(gmock SYSTEM PUBLIC . gmock gtest)
|
||||
|
||||
find_package(Threads)
|
||||
if (Threads_FOUND)
|
||||
target_link_libraries(gmock ${CMAKE_THREAD_LIBS_INIT})
|
||||
else ()
|
||||
target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0)
|
||||
endif ()
|
||||
|
||||
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
|
||||
|
||||
if (MSVC)
|
||||
# Workaround a bug in implementation of variadic templates in MSVC11.
|
||||
target_compile_definitions(gmock PUBLIC _VARIADIC_MAX=10)
|
||||
|
||||
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
||||
target_compile_definitions(gmock PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# Disable MSVC warnings of POSIX functions.
|
||||
target_compile_options(gmock PUBLIC -Wno-deprecated-declarations)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# GTest doesn't detect <tuple> with clang.
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
target_compile_definitions(gmock PUBLIC GTEST_USE_OWN_TR1_TUPLE=1)
|
||||
endif ()
|
||||
|
||||
# Silence MSVC tr1 deprecation warning in gmock.
|
||||
target_compile_definitions(gmock
|
||||
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Build the actual library tests
|
||||
add_subdirectory(gtest)
|
||||
|
||||
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
||||
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
||||
target_include_directories(test-main SYSTEM PUBLIC gtest gmock)
|
||||
target_link_libraries(test-main gmock fmt)
|
||||
target_include_directories(test-main PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
target_link_libraries(test-main gtest)
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
# Workaround GTest bug https://github.com/google/googletest/issues/705.
|
||||
check_cxx_compiler_flag(
|
||||
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
||||
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
||||
target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks)
|
||||
endif ()
|
||||
|
||||
# Use less strict pedantic flags for the tests because GMock doesn't compile
|
||||
# cleanly with -pedantic and -std=c++98.
|
||||
if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
|
||||
#set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros)
|
||||
endif ()
|
||||
|
||||
function(add_fmt_executable name)
|
||||
add_executable(${name} ${ARGN})
|
||||
if (MINGW)
|
||||
target_link_libraries(${name} -static-libgcc -static-libstdc++)
|
||||
endif ()
|
||||
# (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_link_libraries(${name} -Wno-stringop-overflow)
|
||||
endif ()
|
||||
endfunction()
|
||||
|
||||
# Adds a test.
|
||||
# Usage: add_fmt_test(name srcs...)
|
||||
function(add_fmt_test name)
|
||||
add_fmt_executable(${name} ${name}.cc ${ARGN})
|
||||
target_link_libraries(${name} test-main)
|
||||
cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY;MODULE" "" "" ${ARGN})
|
||||
|
||||
set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS})
|
||||
if (ADD_FMT_TEST_HEADER_ONLY)
|
||||
set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc)
|
||||
set(libs gtest fmt-header-only)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
|
||||
endif ()
|
||||
elseif (ADD_FMT_TEST_MODULE)
|
||||
set(libs gtest test-module)
|
||||
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
|
||||
else ()
|
||||
set(libs test-main fmt)
|
||||
endif ()
|
||||
add_fmt_executable(${name} ${sources})
|
||||
target_link_libraries(${name} ${libs})
|
||||
|
||||
# Define if certain C++ features can be used.
|
||||
if (FMT_PEDANTIC)
|
||||
@ -83,39 +53,78 @@ function(add_fmt_test name)
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(${name} PRIVATE ${WERROR_FLAG})
|
||||
endif ()
|
||||
target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
|
||||
add_test(NAME ${name} COMMAND ${name})
|
||||
endfunction()
|
||||
|
||||
add_fmt_test(args-test)
|
||||
add_fmt_test(assert-test)
|
||||
add_fmt_test(chrono-test)
|
||||
add_fmt_test(color-test)
|
||||
add_fmt_test(core-test)
|
||||
add_fmt_test(grisu-test)
|
||||
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
|
||||
add_fmt_test(gtest-extra-test)
|
||||
add_fmt_test(format-test mock-allocator.h)
|
||||
if (MSVC)
|
||||
target_compile_options(format-test PRIVATE /bigobj)
|
||||
endif ()
|
||||
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||
add_fmt_test(format-impl-test)
|
||||
add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc)
|
||||
endif ()
|
||||
add_fmt_test(locale-test)
|
||||
add_fmt_test(ostream-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(custom-formatter-test)
|
||||
add_fmt_test(ranges-test)
|
||||
add_fmt_test(ranges-test ranges-odr-test.cc)
|
||||
add_fmt_test(scan-test)
|
||||
add_fmt_test(unicode-test HEADER_ONLY)
|
||||
if (MSVC)
|
||||
target_compile_options(unicode-test PRIVATE /utf-8)
|
||||
endif ()
|
||||
add_fmt_test(xchar-test)
|
||||
add_fmt_test(enforce-checks-test)
|
||||
target_compile_definitions(enforce-checks-test PRIVATE
|
||||
-DFMT_ENFORCE_COMPILE_STRING)
|
||||
|
||||
if (NOT MSVC_BUILD_STATIC)
|
||||
if (FMT_CAN_MODULE)
|
||||
# The tests need {fmt} to be compiled as traditional library
|
||||
# because of visibility of implementation details.
|
||||
# If module support is present the module tests require a
|
||||
# test-only module to be built from {fmt}
|
||||
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
|
||||
target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES})
|
||||
target_include_directories(test-module PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
enable_module(test-module)
|
||||
|
||||
add_fmt_test(module-test MODULE test-main.cc)
|
||||
if (MSVC)
|
||||
target_compile_options(test-module PRIVATE /utf-8 /Zc:__cplusplus
|
||||
/Zc:externConstexpr /Zc:inline)
|
||||
target_compile_options(module-test PRIVATE /utf-8 /Zc:__cplusplus
|
||||
/Zc:externConstexpr /Zc:inline)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
|
||||
foreach (flag_var
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
if (${flag_var} MATCHES "^(/|-)(MT|MTd)")
|
||||
set(MSVC_STATIC_RUNTIME ON)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if (NOT MSVC_STATIC_RUNTIME)
|
||||
add_fmt_executable(posix-mock-test
|
||||
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
||||
target_include_directories(
|
||||
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(posix-mock-test gmock)
|
||||
target_include_directories(posix-mock-test SYSTEM PUBLIC gtest gmock)
|
||||
target_link_libraries(posix-mock-test gtest)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
@ -126,34 +135,16 @@ if (NOT MSVC_BUILD_STATIC)
|
||||
add_fmt_test(os-test)
|
||||
endif ()
|
||||
|
||||
add_fmt_executable(header-only-test
|
||||
header-only-test.cc header-only-test2.cc test-main.cc)
|
||||
target_link_libraries(header-only-test gmock)
|
||||
target_include_directories(header-only-test SYSTEM PUBLIC gtest gmock)
|
||||
if (TARGET fmt-header-only)
|
||||
target_link_libraries(header-only-test fmt-header-only)
|
||||
else ()
|
||||
target_include_directories(
|
||||
header-only-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(header-only-test PRIVATE FMT_HEADER_ONLY=1)
|
||||
endif ()
|
||||
|
||||
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
|
||||
|
||||
if (FMT_PEDANTIC)
|
||||
# MSVC fails to compile GMock when C++17 is enabled.
|
||||
if (FMT_HAS_VARIANT AND NOT MSVC)
|
||||
add_fmt_test(std-format-test)
|
||||
set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17)
|
||||
endif ()
|
||||
|
||||
# Test that the library can be compiled with exceptions disabled.
|
||||
# -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822.
|
||||
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
|
||||
check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG)
|
||||
endif ()
|
||||
if (HAVE_FNO_EXCEPTIONS_FLAG)
|
||||
add_library(noexception-test ../src/format.cc)
|
||||
add_library(noexception-test ../src/format.cc noexception-test.cc)
|
||||
target_include_directories(
|
||||
noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(noexception-test PRIVATE -fno-exceptions)
|
||||
@ -168,7 +159,11 @@ if (FMT_PEDANTIC)
|
||||
nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(
|
||||
nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1)
|
||||
endif ()
|
||||
|
||||
# These tests are disabled on Windows because they take too long.
|
||||
if (FMT_PEDANTIC AND NOT WIN32)
|
||||
# Test if incorrect API usages produce compilation error.
|
||||
add_test(compile-error-test ${CMAKE_CTEST_COMMAND}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test"
|
||||
@ -177,14 +172,11 @@ if (FMT_PEDANTIC)
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}"
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
"-DFMT_DIR=${CMAKE_SOURCE_DIR}"
|
||||
"-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}")
|
||||
endif ()
|
||||
|
||||
# These tests are disabled on Windows because they take too long.
|
||||
if (FMT_PEDANTIC AND NOT WIN32)
|
||||
# Test if the targets are found from the build directory.
|
||||
add_test(find-package-test ${CMAKE_CTEST_COMMAND}
|
||||
-C ${CMAKE_BUILD_TYPE}
|
||||
@ -195,6 +187,7 @@ if (FMT_PEDANTIC AND NOT WIN32)
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DFMT_DIR=${PROJECT_BINARY_DIR}"
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
@ -215,6 +208,21 @@ if (FMT_PEDANTIC AND NOT WIN32)
|
||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||
endif ()
|
||||
|
||||
# This test are disabled on Windows because it is only *NIX issue.
|
||||
if (FMT_PEDANTIC AND NOT WIN32)
|
||||
add_test(static-export-test ${CMAKE_CTEST_COMMAND}
|
||||
-C ${CMAKE_BUILD_TYPE}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/static-export-test"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/static-export-test"
|
||||
--build-generator ${CMAKE_GENERATOR}
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||
endif ()
|
||||
|
||||
# Activate optional CUDA tests if CUDA is found. For version selection see
|
||||
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
||||
if (FMT_CUDA_TEST)
|
||||
|
@ -1,17 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
|
||||
project(fmt-test)
|
||||
project(fmt-test CXX)
|
||||
|
||||
add_subdirectory(../.. fmt)
|
||||
|
||||
add_executable(library-test "main.cc")
|
||||
target_link_libraries(library-test fmt::fmt)
|
||||
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
add_executable(library-test main.cc)
|
||||
target_include_directories(library-test PUBLIC SYSTEM .)
|
||||
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
target_link_libraries(library-test fmt::fmt)
|
||||
|
||||
if (TARGET fmt::fmt-header-only)
|
||||
add_executable(header-only-test "main.cc")
|
||||
target_link_libraries(header-only-test fmt::fmt-header-only)
|
||||
target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
add_executable(header-only-test main.cc)
|
||||
target_include_directories(header-only-test PUBLIC SYSTEM .)
|
||||
target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
target_link_libraries(header-only-test fmt::fmt-header-only)
|
||||
endif ()
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/core.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
for(int i = 0; i < argc; ++i)
|
||||
fmt::print("{}: {}\n", i, argv[i]);
|
||||
for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]);
|
||||
}
|
||||
|
186
test/args-test.cc
Normal file
186
test/args-test.cc
Normal file
@ -0,0 +1,186 @@
|
||||
// Formatting library for C++ - dynamic argument store tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/args.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(args_test, basic) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc1");
|
||||
store.push_back(1.5f);
|
||||
EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store));
|
||||
}
|
||||
|
||||
TEST(args_test, strings_and_refs) {
|
||||
// Unfortunately the tests are compiled with old ABI so strings use COW.
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char str[] = "1234567890";
|
||||
store.push_back(str);
|
||||
store.push_back(std::cref(str));
|
||||
store.push_back(fmt::string_view{str});
|
||||
str[0] = 'X';
|
||||
|
||||
auto result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("1234567890 and X234567890 and X234567890", result);
|
||||
}
|
||||
|
||||
struct custom_type {
|
||||
int i = 0;
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<custom_type> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return format_to(ctx.out(), "cust={}", p.i);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(args_test, custom_format) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
auto c = custom_type();
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(std::cref(c));
|
||||
++c.i;
|
||||
auto result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
struct to_stringable {
|
||||
friend fmt::string_view to_string_view(to_stringable) { return {}; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<to_stringable> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto format(to_stringable, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(args_test, to_string_and_formatter) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
auto s = to_stringable();
|
||||
store.push_back(s);
|
||||
store.push_back(std::cref(s));
|
||||
fmt::vformat("", store);
|
||||
}
|
||||
|
||||
TEST(args_test, named_int) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("a1", 42));
|
||||
EXPECT_EQ("42", fmt::vformat("{a1}", store));
|
||||
}
|
||||
|
||||
TEST(args_test, named_strings) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char str[] = "1234567890";
|
||||
store.push_back(fmt::arg("a1", str));
|
||||
store.push_back(fmt::arg("a2", std::cref(str)));
|
||||
str[0] = 'X';
|
||||
EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store));
|
||||
}
|
||||
|
||||
TEST(args_test, named_arg_by_ref) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char band[] = "Rolling Stones";
|
||||
store.push_back(fmt::arg("band", std::cref(band)));
|
||||
band[9] = 'c'; // Changing band affects the output.
|
||||
EXPECT_EQ(fmt::vformat("{band}", store), "Rolling Scones");
|
||||
}
|
||||
|
||||
TEST(args_test, named_custom_format) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
auto c = custom_type();
|
||||
store.push_back(fmt::arg("c1", c));
|
||||
++c.i;
|
||||
store.push_back(fmt::arg("c2", c));
|
||||
++c.i;
|
||||
store.push_back(fmt::arg("c_ref", std::cref(c)));
|
||||
++c.i;
|
||||
auto result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
TEST(args_test, clear) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
|
||||
auto result = fmt::vformat("{}", store);
|
||||
EXPECT_EQ("42", result);
|
||||
|
||||
store.push_back(43);
|
||||
result = fmt::vformat("{} and {}", store);
|
||||
EXPECT_EQ("42 and 43", result);
|
||||
|
||||
store.clear();
|
||||
store.push_back(44);
|
||||
result = fmt::vformat("{}", store);
|
||||
EXPECT_EQ("44", result);
|
||||
}
|
||||
|
||||
TEST(args_test, reserve) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.reserve(2, 1);
|
||||
store.push_back(1.5f);
|
||||
store.push_back(fmt::arg("a1", 42));
|
||||
auto result = fmt::vformat("{a1} and {}", store);
|
||||
EXPECT_EQ("42 and 1.5", result);
|
||||
}
|
||||
|
||||
struct copy_throwable {
|
||||
copy_throwable() {}
|
||||
copy_throwable(const copy_throwable&) { throw "deal with it"; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<copy_throwable> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(args_test, throw_on_copy) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(std::string("foo"));
|
||||
try {
|
||||
store.push_back(copy_throwable());
|
||||
} catch (...) {
|
||||
}
|
||||
EXPECT_EQ(fmt::vformat("{}", store), "foo");
|
||||
}
|
||||
|
||||
TEST(args_test, move_constructor) {
|
||||
using store_type = fmt::dynamic_format_arg_store<fmt::format_context>;
|
||||
auto store = std::unique_ptr<store_type>(new store_type());
|
||||
store->push_back(42);
|
||||
store->push_back(std::string("foo"));
|
||||
store->push_back(fmt::arg("a1", "foo"));
|
||||
auto moved_store = std::move(*store);
|
||||
store.reset();
|
||||
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
// Formatting library for C++ - assertion tests
|
||||
// Formatting library for C++ - FMT_ASSERT test
|
||||
//
|
||||
// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks
|
||||
// which are slow on some platforms. In other tests FMT_ASSERT is made to throw
|
||||
// an exception which is much faster and easier to check.
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
@ -6,9 +10,9 @@
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/core.h"
|
||||
#include "gtest.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(AssertTest, Fail) {
|
||||
TEST(assert_test, fail) {
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||
#else
|
||||
@ -16,9 +20,8 @@ TEST(AssertTest, Fail) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool test_condition = false;
|
||||
|
||||
TEST(AssertTest, DanglingElse) {
|
||||
TEST(assert_test, dangling_else) {
|
||||
bool test_condition = false;
|
||||
bool executed_else = false;
|
||||
if (test_condition)
|
||||
FMT_ASSERT(true, "");
|
||||
|
@ -5,77 +5,221 @@
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifdef WIN32
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/chrono.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest-extra.h"
|
||||
#include "gtest-extra.h" // EXPECT_THROW_MSG
|
||||
#include "util.h" // get_locale
|
||||
|
||||
std::tm make_tm() {
|
||||
using fmt::runtime;
|
||||
|
||||
using testing::Contains;
|
||||
|
||||
auto make_tm() -> std::tm {
|
||||
auto time = std::tm();
|
||||
time.tm_mday = 1;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm make_hour(int h) {
|
||||
auto make_hour(int h) -> std::tm {
|
||||
auto time = make_tm();
|
||||
time.tm_hour = h;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm make_minute(int m) {
|
||||
auto make_minute(int m) -> std::tm {
|
||||
auto time = make_tm();
|
||||
time.tm_min = m;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm make_second(int s) {
|
||||
auto make_second(int s) -> std::tm {
|
||||
auto time = make_tm();
|
||||
time.tm_sec = s;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::string format_tm(const std::tm& time, const char* spec,
|
||||
const std::locale& loc) {
|
||||
std::string system_strftime(const std::string& format, const std::tm* timeptr,
|
||||
std::locale* locptr = nullptr) {
|
||||
auto loc = locptr ? *locptr : std::locale::classic();
|
||||
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
||||
std::ostringstream os;
|
||||
os.imbue(loc);
|
||||
facet.put(os, os, ' ', &time, spec, spec + std::strlen(spec));
|
||||
facet.put(os, os, ' ', 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 == "-0000") str = "+0000";
|
||||
return str;
|
||||
#else
|
||||
return os.str();
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(TimeTest, Format) {
|
||||
std::tm tm = std::tm();
|
||||
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
|
||||
int sec) {
|
||||
auto tm = std::tm();
|
||||
tm.tm_sec = sec;
|
||||
tm.tm_min = min;
|
||||
tm.tm_hour = hour;
|
||||
tm.tm_mday = mday;
|
||||
tm.tm_mon = mon - 1;
|
||||
tm.tm_year = year - 1900;
|
||||
return tm;
|
||||
}
|
||||
|
||||
TEST(chrono_test, format_tm) {
|
||||
auto tm = std::tm();
|
||||
tm.tm_year = 116;
|
||||
tm.tm_mon = 3;
|
||||
tm.tm_mday = 25;
|
||||
EXPECT_EQ("The date is 2016-04-25.",
|
||||
fmt::format("The date is {:%Y-%m-%d}.", tm));
|
||||
tm.tm_hour = 11;
|
||||
tm.tm_min = 22;
|
||||
tm.tm_sec = 33;
|
||||
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
|
||||
"The date is 2016-04-25 11:22:33.");
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "2016");
|
||||
EXPECT_EQ(fmt::format("{:%C}", tm), "20");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
|
||||
EXPECT_EQ(fmt::format("{:%e}", tm), "25");
|
||||
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16");
|
||||
EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25");
|
||||
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
|
||||
|
||||
// Short year
|
||||
tm.tm_year = 999 - 1900;
|
||||
tm.tm_mon = 0; // for %G
|
||||
tm.tm_mday = 2; // for %G
|
||||
tm.tm_wday = 3; // for %G
|
||||
tm.tm_yday = 1; // for %G
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "0999");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), "0999");
|
||||
EXPECT_EQ(fmt::format("{:%G}", tm), "0999");
|
||||
|
||||
tm.tm_year = 27 - 1900;
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "0027");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), "0027");
|
||||
|
||||
// Overflow year
|
||||
tm.tm_year = 2147483647;
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "2147485547");
|
||||
|
||||
tm.tm_year = -2147483648;
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "-2147481748");
|
||||
|
||||
// for week on the year
|
||||
// https://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
||||
std::vector<std::tm> tm_list = {
|
||||
make_tm(1975, 12, 29, 12, 14, 16), // W01
|
||||
make_tm(1977, 1, 2, 12, 14, 16), // W53
|
||||
make_tm(1999, 12, 27, 12, 14, 16), // W52
|
||||
make_tm(1999, 12, 31, 12, 14, 16), // W52
|
||||
make_tm(2000, 1, 1, 12, 14, 16), // W52
|
||||
make_tm(2000, 1, 2, 12, 14, 16), // W52
|
||||
make_tm(2000, 1, 3, 12, 14, 16) // W1
|
||||
};
|
||||
const std::string iso_week_spec = "%Y-%m-%d: %G %g %V";
|
||||
for (auto ctm : tm_list) {
|
||||
// Calculate tm_yday, tm_wday, etc.
|
||||
std::time_t t = std::mktime(&ctm);
|
||||
tm = *std::localtime(&t);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", iso_week_spec);
|
||||
EXPECT_EQ(system_strftime(iso_week_spec, &tm),
|
||||
fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
|
||||
// Every day from 1970-01-01
|
||||
std::time_t time_now = std::time(nullptr);
|
||||
for (std::time_t t = 6 * 3600; t < time_now; t += 86400) {
|
||||
tm = *std::localtime(&t);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", iso_week_spec);
|
||||
EXPECT_EQ(system_strftime(iso_week_spec, &tm),
|
||||
fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TimeTest, GrowBuffer) {
|
||||
std::string s = "{:";
|
||||
// MSVC:
|
||||
// minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(971) : Assertion failed:
|
||||
// timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099
|
||||
#ifndef _WIN32
|
||||
TEST(chrono_test, format_tm_future) {
|
||||
auto tm = std::tm();
|
||||
tm.tm_year = 10445; // 10000+ years
|
||||
tm.tm_mon = 3;
|
||||
tm.tm_mday = 25;
|
||||
tm.tm_hour = 11;
|
||||
tm.tm_min = 22;
|
||||
tm.tm_sec = 33;
|
||||
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
|
||||
"The date is 12345-04-25 11:22:33.");
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "12345");
|
||||
EXPECT_EQ(fmt::format("{:%C}", tm), "123");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
|
||||
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/45");
|
||||
EXPECT_EQ(fmt::format("{:%F}", tm), "12345-04-25");
|
||||
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
|
||||
}
|
||||
|
||||
TEST(chrono_test, format_tm_past) {
|
||||
auto tm = std::tm();
|
||||
tm.tm_year = -2001;
|
||||
tm.tm_mon = 3;
|
||||
tm.tm_mday = 25;
|
||||
tm.tm_hour = 11;
|
||||
tm.tm_min = 22;
|
||||
tm.tm_sec = 33;
|
||||
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
|
||||
"The date is -101-04-25 11:22:33.");
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "-101");
|
||||
|
||||
// macOS %C - "-1"
|
||||
// Linux %C - "-2"
|
||||
// fmt %C - "-1"
|
||||
EXPECT_EQ(fmt::format("{:%C}", tm), "-1");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
|
||||
|
||||
// macOS %D - "04/25/01" (%y)
|
||||
// Linux %D - "04/25/99" (%y)
|
||||
// fmt %D - "04/25/01" (%y)
|
||||
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/01");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%F}", tm), "-101-04-25");
|
||||
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
|
||||
|
||||
tm.tm_year = -1901; // -1
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "-001");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
|
||||
|
||||
tm.tm_year = -1911; // -11
|
||||
EXPECT_EQ(fmt::format("{:%Y}", tm), "-011");
|
||||
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(chrono_test, grow_buffer) {
|
||||
auto s = std::string("{:");
|
||||
for (int i = 0; i < 30; ++i) s += "%c";
|
||||
s += "}\n";
|
||||
std::time_t t = std::time(nullptr);
|
||||
fmt::format(s, *std::localtime(&t));
|
||||
auto t = std::time(nullptr);
|
||||
(void)fmt::format(fmt::runtime(s), *std::localtime(&t));
|
||||
}
|
||||
|
||||
TEST(TimeTest, FormatToEmptyContainer) {
|
||||
std::string s;
|
||||
TEST(chrono_test, format_to_empty_container) {
|
||||
auto time = std::tm();
|
||||
time.tm_sec = 42;
|
||||
auto s = std::string();
|
||||
fmt::format_to(std::back_inserter(s), "{:%S}", time);
|
||||
EXPECT_EQ(s, "42");
|
||||
}
|
||||
|
||||
TEST(TimeTest, EmptyResult) { EXPECT_EQ("", fmt::format("{}", std::tm())); }
|
||||
TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
|
||||
|
||||
static bool EqualTime(const std::tm& lhs, const std::tm& rhs) {
|
||||
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 &&
|
||||
@ -83,28 +227,62 @@ static bool EqualTime(const std::tm& lhs, const std::tm& rhs) {
|
||||
lhs.tm_isdst == rhs.tm_isdst;
|
||||
}
|
||||
|
||||
TEST(TimeTest, LocalTime) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(EqualTime(tm, fmt::localtime(t)));
|
||||
TEST(chrono_test, localtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
||||
}
|
||||
|
||||
TEST(TimeTest, GMTime) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm tm = *std::gmtime(&t);
|
||||
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t)));
|
||||
TEST(chrono_test, gmtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::gmtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
||||
}
|
||||
|
||||
#define EXPECT_TIME(spec, time, duration) \
|
||||
{ \
|
||||
std::locale loc("ja_JP.utf8"); \
|
||||
EXPECT_EQ(format_tm(time, spec, loc), \
|
||||
fmt::format(loc, "{:" spec "}", duration)); \
|
||||
template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string {
|
||||
auto t = std::chrono::system_clock::to_time_t(tp);
|
||||
auto tm = *std::localtime(&t);
|
||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||
}
|
||||
|
||||
TEST(chrono_test, time_point) {
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||
EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1));
|
||||
using time_point =
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
|
||||
auto t2 = time_point(std::chrono::seconds(42));
|
||||
EXPECT_EQ(strftime_full(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"};
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows because these formats are not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
#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), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
TEST(ChronoTest, FormatDefault) {
|
||||
TEST(chrono_test, format_default) {
|
||||
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ("42as",
|
||||
fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
|
||||
@ -146,49 +324,7 @@ TEST(ChronoTest, FormatDefault) {
|
||||
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatWide) {
|
||||
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ(L"42as",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::atto>(42)));
|
||||
EXPECT_EQ(L"42fs",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::femto>(42)));
|
||||
EXPECT_EQ(L"42ps",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::pico>(42)));
|
||||
EXPECT_EQ(L"42ns", fmt::format(L"{}", std::chrono::nanoseconds(42)));
|
||||
EXPECT_EQ(L"42\u00B5s", fmt::format(L"{}", std::chrono::microseconds(42)));
|
||||
EXPECT_EQ(L"42ms", fmt::format(L"{}", std::chrono::milliseconds(42)));
|
||||
EXPECT_EQ(L"42cs",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::centi>(42)));
|
||||
EXPECT_EQ(L"42ds",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::deci>(42)));
|
||||
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ(L"42das",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::deca>(42)));
|
||||
EXPECT_EQ(L"42hs",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::hecto>(42)));
|
||||
EXPECT_EQ(L"42ks",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::kilo>(42)));
|
||||
EXPECT_EQ(L"42Ms",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::mega>(42)));
|
||||
EXPECT_EQ(L"42Gs",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::giga>(42)));
|
||||
EXPECT_EQ(L"42Ts",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::tera>(42)));
|
||||
EXPECT_EQ(L"42Ps",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::peta>(42)));
|
||||
EXPECT_EQ(L"42Es",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::exa>(42)));
|
||||
EXPECT_EQ(L"42m", fmt::format(L"{}", std::chrono::minutes(42)));
|
||||
EXPECT_EQ(L"42h", fmt::format(L"{}", std::chrono::hours(42)));
|
||||
EXPECT_EQ(
|
||||
L"42[15]s",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
|
||||
EXPECT_EQ(
|
||||
L"42[15/4]s",
|
||||
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, Align) {
|
||||
TEST(chrono_test, align) {
|
||||
auto s = std::chrono::seconds(42);
|
||||
EXPECT_EQ("42s ", fmt::format("{:5}", s));
|
||||
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
|
||||
@ -204,7 +340,7 @@ TEST(ChronoTest, Align) {
|
||||
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatSpecs) {
|
||||
TEST(chrono_test, format_specs) {
|
||||
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
|
||||
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
|
||||
EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0)));
|
||||
@ -233,43 +369,64 @@ TEST(ChronoTest, FormatSpecs) {
|
||||
EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, InvalidSpecs) {
|
||||
TEST(chrono_test, invalid_specs) {
|
||||
auto sec = std::chrono::seconds(0);
|
||||
EXPECT_THROW_MSG(fmt::format("{:%a}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%A}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%c}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%x}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%Ex}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%X}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%EX}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%D}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%F}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%Ec}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%w}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%u}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%b}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%B}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%z}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%Z}", sec), fmt::format_error, "no date");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%Eq}", sec), fmt::format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%a}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%A}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%c}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%x}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Ex}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%X}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%EX}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%D}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%F}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Ec}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%w}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%u}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%b}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%B}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%z}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Z}"), sec), fmt::format_error,
|
||||
"no date");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Eq}"), sec), fmt::format_error,
|
||||
"invalid format");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%Oq}", sec), fmt::format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
|
||||
"invalid format");
|
||||
}
|
||||
|
||||
TEST(ChronoTest, Locale) {
|
||||
const char* loc_name = "ja_JP.utf8";
|
||||
bool has_locale = false;
|
||||
std::locale loc;
|
||||
try {
|
||||
loc = std::locale(loc_name);
|
||||
has_locale = true;
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
if (!has_locale) {
|
||||
fmt::print("{} locale is missing.\n", loc_name);
|
||||
return;
|
||||
}
|
||||
auto format_tm(const std::tm& time, fmt::string_view spec,
|
||||
const std::locale& loc) -> std::string {
|
||||
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
||||
std::ostringstream os;
|
||||
os.imbue(loc);
|
||||
facet.put(os, os, ' ', &time, spec.begin(), spec.end());
|
||||
return os.str();
|
||||
}
|
||||
|
||||
TEST(chrono_test, locale) {
|
||||
auto loc = get_locale("ja_JP.utf8");
|
||||
if (loc == std::locale::classic()) return;
|
||||
# define EXPECT_TIME(spec, time, duration) \
|
||||
{ \
|
||||
auto jp_loc = std::locale("ja_JP.utf8"); \
|
||||
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
||||
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
||||
}
|
||||
EXPECT_TIME("%OH", 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));
|
||||
@ -283,9 +440,9 @@ TEST(ChronoTest, Locale) {
|
||||
EXPECT_TIME("%p", time, sec);
|
||||
}
|
||||
|
||||
typedef std::chrono::duration<double, std::milli> dms;
|
||||
using dms = std::chrono::duration<double, std::milli>;
|
||||
|
||||
TEST(ChronoTest, FormatDefaultFP) {
|
||||
TEST(chrono_test, format_default_fp) {
|
||||
typedef std::chrono::duration<float> fs;
|
||||
EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
|
||||
typedef std::chrono::duration<float, std::milli> fms;
|
||||
@ -295,24 +452,37 @@ TEST(ChronoTest, FormatDefaultFP) {
|
||||
EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatPrecision) {
|
||||
EXPECT_THROW_MSG(fmt::format("{:.2}", std::chrono::seconds(42)),
|
||||
fmt::format_error,
|
||||
"precision not allowed for this argument type");
|
||||
TEST(chrono_test, format_precision) {
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
|
||||
fmt::format_error, "precision not allowed for this argument type");
|
||||
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
|
||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
|
||||
|
||||
EXPECT_EQ("13ms", fmt::format("{:.0}", dms(12.56)));
|
||||
EXPECT_EQ("12.6ms", fmt::format("{:.1}", dms(12.56)));
|
||||
EXPECT_EQ("12.56ms", fmt::format("{:.2}", dms(12.56)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatFullSpecs) {
|
||||
TEST(chrono_test, format_full_specs) {
|
||||
EXPECT_EQ("1ms ", fmt::format("{:6.0}", dms(1.234)));
|
||||
EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
|
||||
EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
|
||||
EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
|
||||
EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8));
|
||||
EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3));
|
||||
EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
|
||||
|
||||
EXPECT_EQ("13ms ", fmt::format("{:6.0}", dms(12.56)));
|
||||
EXPECT_EQ(" 13ms", fmt::format("{:>8.{}}", dms(12.56), 0));
|
||||
EXPECT_EQ(" 13ms ", fmt::format("{:^{}.{}}", dms(12.56), 6, 0));
|
||||
EXPECT_EQ(" 13ms ", fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8));
|
||||
EXPECT_EQ("==13ms===", fmt::format("{:=^{}.{}}", dms(12.56), 9, 0));
|
||||
EXPECT_EQ("***13ms***", fmt::format("{:*^10.0}", dms(12.56)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatSimpleQq) {
|
||||
TEST(chrono_test, format_simple_q) {
|
||||
typedef std::chrono::duration<float> fs;
|
||||
EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
|
||||
typedef std::chrono::duration<float, std::milli> fms;
|
||||
@ -322,34 +492,42 @@ TEST(ChronoTest, FormatSimpleQq) {
|
||||
EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatPrecisionQq) {
|
||||
EXPECT_THROW_MSG(fmt::format("{:.2%Q %q}", std::chrono::seconds(42)),
|
||||
fmt::format_error,
|
||||
"precision not allowed for this argument type");
|
||||
TEST(chrono_test, format_precision_q) {
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)),
|
||||
fmt::format_error, "precision not allowed for this argument type");
|
||||
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
|
||||
EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatFullSpecsQq) {
|
||||
TEST(chrono_test, format_full_specs_q) {
|
||||
EXPECT_EQ("1 ms ", fmt::format("{:7.0%Q %q}", dms(1.234)));
|
||||
EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
|
||||
EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
|
||||
EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
|
||||
EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9));
|
||||
EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3));
|
||||
EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
|
||||
|
||||
EXPECT_EQ("13 ms ", fmt::format("{:7.0%Q %q}", dms(12.56)));
|
||||
EXPECT_EQ(" 13 ms", fmt::format("{:>8.{}%Q %q}", dms(12.56), 0));
|
||||
EXPECT_EQ(" 13 ms ", fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0));
|
||||
EXPECT_EQ(" 13 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9));
|
||||
EXPECT_EQ("==13 ms==", fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0));
|
||||
EXPECT_EQ("***13 ms***", fmt::format("{:*^11.0%Q %q}", dms(12.56)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, InvalidWidthId) {
|
||||
EXPECT_THROW(fmt::format("{:{o}", std::chrono::seconds(0)),
|
||||
TEST(chrono_test, invalid_width_id) {
|
||||
EXPECT_THROW((void)fmt::format(runtime("{:{o}"), std::chrono::seconds(0)),
|
||||
fmt::format_error);
|
||||
}
|
||||
|
||||
TEST(ChronoTest, InvalidColons) {
|
||||
EXPECT_THROW(fmt::format("{0}=:{0::", std::chrono::seconds(0)),
|
||||
TEST(chrono_test, invalid_colons) {
|
||||
EXPECT_THROW((void)fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)),
|
||||
fmt::format_error);
|
||||
}
|
||||
|
||||
TEST(ChronoTest, NegativeDurations) {
|
||||
TEST(chrono_test, negative_durations) {
|
||||
EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
|
||||
EXPECT_EQ("-03:25:45",
|
||||
fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
|
||||
@ -364,16 +542,13 @@ TEST(ChronoTest, NegativeDurations) {
|
||||
fmt::format("{:%Q}", std::chrono::duration<int>(min)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, SpecialDurations) {
|
||||
EXPECT_EQ(
|
||||
"40.",
|
||||
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
|
||||
TEST(chrono_test, special_durations) {
|
||||
auto value = fmt::format("{:%S}", std::chrono::duration<double>(1e20));
|
||||
EXPECT_EQ(value, "40");
|
||||
auto nan = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_EQ(
|
||||
"nan nan nan nan nan:nan nan",
|
||||
fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)));
|
||||
fmt::format("{:%S}",
|
||||
std::chrono::duration<float, std::atto>(1.79400457e+31f));
|
||||
EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)),
|
||||
"1Es");
|
||||
EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)),
|
||||
@ -384,4 +559,67 @@ TEST(ChronoTest, SpecialDurations) {
|
||||
"03:33:20");
|
||||
}
|
||||
|
||||
TEST(chrono_test, unsigned_duration) {
|
||||
EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
|
||||
}
|
||||
|
||||
TEST(chrono_test, weekday) {
|
||||
auto loc = get_locale("ru_RU.UTF-8");
|
||||
std::locale::global(loc);
|
||||
auto mon = fmt::weekday(1);
|
||||
|
||||
auto tm = std::tm();
|
||||
tm.tm_wday = static_cast<int>(mon.c_encoding());
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", mon), "Mon");
|
||||
EXPECT_EQ(fmt::format("{:%a}", tm), "Mon");
|
||||
|
||||
if (loc != std::locale::classic()) {
|
||||
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
|
||||
Contains(fmt::format(loc, "{:L}", mon)));
|
||||
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
|
||||
Contains(fmt::format(loc, "{:%a}", tm)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
using attoseconds = std::chrono::duration<long long, std::atto>;
|
||||
// Check that 18 digits of subsecond precision are supported.
|
||||
EXPECT_EQ(fmt::format("{:%S}", attoseconds{999999999999999999}),
|
||||
"00.999999999999999999");
|
||||
EXPECT_EQ(fmt::format("{:%S}", attoseconds{673231113420148734}),
|
||||
"00.673231113420148734");
|
||||
EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673231113420148734}),
|
||||
"-00.673231113420148734");
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13420148734}),
|
||||
"13.420148734");
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
||||
"-13.420148734");
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
|
||||
{
|
||||
// Check that {:%H:%M:%S} is equivalent to {:%T}.
|
||||
auto dur = std::chrono::milliseconds{3601234};
|
||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||
EXPECT_EQ(formatted_dur, "01:00:01.234");
|
||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||
}
|
||||
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{9123456789}), "09.123456789");
|
||||
// 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{99123000000}), "39.123000000");
|
||||
{
|
||||
// Now the hour is printed, and we also test if negative doubles work.
|
||||
auto dur = nanoseconds_dbl{-99123456789};
|
||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||
}
|
||||
// Check that durations with precision greater than std::chrono::seconds have
|
||||
// fixed precision, and print zeros even if there is no fractional part.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
||||
"07.000000");
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
@ -7,62 +7,29 @@
|
||||
|
||||
#include "fmt/color.h"
|
||||
|
||||
#include "gtest-extra.h"
|
||||
#include <iterator> // std::back_inserter
|
||||
|
||||
TEST(ColorsTest, ColorsPrint) {
|
||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::color::blue), "blue"),
|
||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||
EXPECT_WRITE(
|
||||
stdout,
|
||||
fmt::print(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
|
||||
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
|
||||
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::bold, "bold"),
|
||||
"\x1b[1mbold\x1b[0m");
|
||||
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::italic, "italic"),
|
||||
"\x1b[3mitalic\x1b[0m");
|
||||
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::underline, "underline"),
|
||||
"\x1b[4munderline\x1b[0m");
|
||||
EXPECT_WRITE(stdout,
|
||||
fmt::print(fmt::emphasis::strikethrough, "strikethrough"),
|
||||
"\x1b[9mstrikethrough\x1b[0m");
|
||||
EXPECT_WRITE(
|
||||
stdout,
|
||||
fmt::print(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
|
||||
"\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, fmt::emphasis::bold, "bold error"),
|
||||
"\x1b[1mbold error\x1b[0m");
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, fg(fmt::color::blue), "blue log"),
|
||||
"\x1b[38;2;000;000;255mblue log\x1b[0m");
|
||||
EXPECT_WRITE(stdout, fmt::print(fmt::text_style(), "hi"), "hi");
|
||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::terminal_color::red), "tred"),
|
||||
"\x1b[31mtred\x1b[0m");
|
||||
EXPECT_WRITE(stdout, fmt::print(bg(fmt::terminal_color::cyan), "tcyan"),
|
||||
"\x1b[46mtcyan\x1b[0m");
|
||||
EXPECT_WRITE(stdout,
|
||||
fmt::print(fg(fmt::terminal_color::bright_green), "tbgreen"),
|
||||
"\x1b[92mtbgreen\x1b[0m");
|
||||
EXPECT_WRITE(stdout,
|
||||
fmt::print(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
|
||||
"\x1b[105mtbmagenta\x1b[0m");
|
||||
}
|
||||
#include "gtest-extra.h" // EXPECT_WRITE
|
||||
|
||||
TEST(ColorsTest, Format) {
|
||||
TEST(color_test, format) {
|
||||
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");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
|
||||
L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
|
||||
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::faint, "faint"), "\x1b[2mfaint\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"),
|
||||
"\x1b[3mitalic\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"),
|
||||
"\x1b[4munderline\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::blink, "blink"), "\x1b[5mblink\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::reverse, "reverse"),
|
||||
"\x1b[7mreverse\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::conceal, "conceal"),
|
||||
"\x1b[8mconceal\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"),
|
||||
"\x1b[9mstrikethrough\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
@ -84,3 +51,16 @@ TEST(ColorsTest, Format) {
|
||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
|
||||
"\x1b[31mfoo\x1b[0m");
|
||||
}
|
||||
|
||||
TEST(color_test, format_to) {
|
||||
auto out = std::string();
|
||||
fmt::format_to(std::back_inserter(out), fg(fmt::rgb(255, 20, 30)),
|
||||
"rgb(255,20,30){}{}{}", 1, 2, 3);
|
||||
EXPECT_EQ(fmt::to_string(out),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m");
|
||||
}
|
||||
|
||||
TEST(color_test, print) {
|
||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
}
|
||||
|
@ -1,71 +1,158 @@
|
||||
# Test if compile errors are produced where necessary.
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
project(compile-error-test CXX)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CheckCXXCompilerFlag)
|
||||
set(fmt_headers "
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/xchar.h>
|
||||
")
|
||||
|
||||
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
|
||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG} ${PEDANTIC_COMPILE_FLAGS})
|
||||
set(error_test_names "")
|
||||
set(non_error_test_content "")
|
||||
|
||||
function (generate_source result fragment)
|
||||
set(${result} "
|
||||
#define FMT_HEADER_ONLY 1
|
||||
#include \"fmt/format.h\"
|
||||
int main() {
|
||||
${fragment}
|
||||
}
|
||||
" PARENT_SCOPE)
|
||||
# For error tests (we expect them to produce compilation error):
|
||||
# * adds a name of test into `error_test_names` list
|
||||
# * generates a single source file (with the same name) for each test
|
||||
# For non-error tests (we expect them to compile successfully):
|
||||
# * adds a code segment as separate function to `non_error_test_content`
|
||||
function (expect_compile name code_fragment)
|
||||
cmake_parse_arguments(EXPECT_COMPILE "ERROR" "" "" ${ARGN})
|
||||
string(MAKE_C_IDENTIFIER "${name}" test_name)
|
||||
|
||||
if (EXPECT_COMPILE_ERROR)
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/${test_name}.cc" "
|
||||
${fmt_headers}
|
||||
void ${test_name}() {
|
||||
${code_fragment}
|
||||
}
|
||||
")
|
||||
set(error_test_names_copy "${error_test_names}")
|
||||
list(APPEND error_test_names_copy "${test_name}")
|
||||
set(error_test_names "${error_test_names_copy}" PARENT_SCOPE)
|
||||
else()
|
||||
set(non_error_test_content "
|
||||
${non_error_test_content}
|
||||
void ${test_name}() {
|
||||
${code_fragment}
|
||||
}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction ()
|
||||
|
||||
function (expect_compile code)
|
||||
generate_source(source "${code}")
|
||||
check_cxx_source_compiles("${source}" compiles)
|
||||
if (NOT compiles)
|
||||
set(error_msg "Compile error for: ${code}")
|
||||
endif ()
|
||||
# Unset the CMake cache variable compiles. Otherwise the compile test will
|
||||
# just use cached information next time it runs.
|
||||
unset(compiles CACHE)
|
||||
if (error_msg)
|
||||
message(FATAL_ERROR ${error_msg})
|
||||
# Generates a source file for non-error test with `non_error_test_content` and
|
||||
# CMake project file with all error and single non-error test targets.
|
||||
function (run_tests)
|
||||
set(cmake_targets "")
|
||||
foreach(test_name IN LISTS error_test_names)
|
||||
set(cmake_targets "
|
||||
${cmake_targets}
|
||||
add_library(test-${test_name} ${test_name}.cc)
|
||||
target_link_libraries(test-${test_name} PRIVATE fmt::fmt)
|
||||
")
|
||||
endforeach()
|
||||
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/non_error_test.cc" "
|
||||
${fmt_headers}
|
||||
${non_error_test_content}
|
||||
")
|
||||
set(cmake_targets "
|
||||
${cmake_targets}
|
||||
add_library(non-error-test non_error_test.cc)
|
||||
target_link_libraries(non-error-test PRIVATE fmt::fmt)
|
||||
")
|
||||
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
project(tests CXX)
|
||||
add_subdirectory(${FMT_DIR} fmt)
|
||||
${cmake_targets}
|
||||
")
|
||||
|
||||
set(build_directory "${CMAKE_CURRENT_BINARY_DIR}/test/build")
|
||||
file(MAKE_DIRECTORY "${build_directory}")
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}"
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DCMAKE_GENERATOR=${CMAKE_GENERATOR}"
|
||||
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
|
||||
"-DFMT_DIR=${FMT_DIR}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/test"
|
||||
WORKING_DIRECTORY "${build_directory}"
|
||||
RESULT_VARIABLE result_var
|
||||
OUTPUT_VARIABLE output_var
|
||||
ERROR_VARIABLE output_var)
|
||||
if (NOT result_var EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to configure:\n${output_var}")
|
||||
endif()
|
||||
|
||||
foreach(test_name IN LISTS error_test_names)
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" --build "${build_directory}" --target "test-${test_name}"
|
||||
WORKING_DIRECTORY "${build_directory}"
|
||||
RESULT_VARIABLE result_var
|
||||
OUTPUT_VARIABLE output_var
|
||||
ERROR_QUIET)
|
||||
if (result_var EQUAL 0)
|
||||
message(SEND_ERROR "No compile error for \"${test_name}\":\n${output_var}")
|
||||
endif ()
|
||||
endforeach()
|
||||
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" --build "${build_directory}" --target "non-error-test"
|
||||
WORKING_DIRECTORY "${build_directory}"
|
||||
RESULT_VARIABLE result_var
|
||||
OUTPUT_VARIABLE output_var
|
||||
ERROR_VARIABLE output_var)
|
||||
if (NOT result_var EQUAL 0)
|
||||
message(SEND_ERROR "Compile error for combined non-error test:\n${output_var}")
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
function (expect_compile_error code)
|
||||
generate_source(source "${code}")
|
||||
check_cxx_source_compiles("${source}" compiles)
|
||||
if (compiles)
|
||||
set(error_msg "No compile error for: ${code}")
|
||||
endif ()
|
||||
# Unset the CMake cache variable compiles. Otherwise the compile test will
|
||||
# just use cached information next time it runs.
|
||||
unset(compiles CACHE)
|
||||
if (error_msg)
|
||||
message(FATAL_ERROR ${error_msg})
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
# check if the source file skeleton compiles
|
||||
expect_compile("")
|
||||
expect_compile(check "")
|
||||
expect_compile(check-error "compilation_error" ERROR)
|
||||
|
||||
# Formatting a wide character with a narrow format string is forbidden.
|
||||
expect_compile_error("fmt::format(\"{}\", L'a');")
|
||||
expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');")
|
||||
expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR)
|
||||
|
||||
# Formatting a wide string with a narrow format string is forbidden.
|
||||
expect_compile_error("fmt::format(\"{}\", L\"foo\");")
|
||||
expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");")
|
||||
expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR)
|
||||
|
||||
# Formatting a narrow string with a wide format string is forbidden because
|
||||
# mixing UTF-8 with UTF-16/32 can result in an invalid output.
|
||||
expect_compile_error("fmt::format(L\"{}\", \"foo\");")
|
||||
expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");")
|
||||
expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR)
|
||||
|
||||
# Formatting a wide string with a narrow format string is forbidden.
|
||||
expect_compile_error("
|
||||
expect_compile(cast-to-string "
|
||||
struct S {
|
||||
operator std::string() const { return std::string(); }
|
||||
};
|
||||
fmt::format(\"{}\", std::string(S()));
|
||||
")
|
||||
expect_compile(cast-to-string-error "
|
||||
struct S {
|
||||
operator std::string() const { return std::string(); }
|
||||
};
|
||||
fmt::format(\"{}\", S());
|
||||
" ERROR)
|
||||
|
||||
# Formatting a function
|
||||
expect_compile(format-function "
|
||||
void (*f)();
|
||||
fmt::format(\"{}\", fmt::ptr(f));
|
||||
")
|
||||
expect_compile(format-function-error "
|
||||
void (*f)();
|
||||
fmt::format(\"{}\", f);
|
||||
" ERROR)
|
||||
|
||||
# Make sure that compiler features detected in the header
|
||||
# match the features detected in CMake.
|
||||
@ -74,6 +161,43 @@ if (SUPPORTS_USER_DEFINED_LITERALS)
|
||||
else ()
|
||||
set(supports_udl 0)
|
||||
endif ()
|
||||
expect_compile("#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
|
||||
# error
|
||||
#endif")
|
||||
expect_compile(udl-check "
|
||||
#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
|
||||
# error
|
||||
#endif
|
||||
")
|
||||
|
||||
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
# Compile-time argument type check
|
||||
expect_compile(format-string-number-spec "
|
||||
#ifdef FMT_HAS_CONSTEVAL
|
||||
fmt::format(\"{:d}\", 42);
|
||||
#endif
|
||||
")
|
||||
expect_compile(format-string-number-spec-error "
|
||||
#ifdef FMT_HAS_CONSTEVAL
|
||||
fmt::format(\"{:d}\", \"I am not a number\");
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
" ERROR)
|
||||
|
||||
# Compile-time argument name check
|
||||
expect_compile(format-string-name "
|
||||
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
using namespace fmt::literals;
|
||||
fmt::print(\"{foo}\", \"foo\"_a=42);
|
||||
#endif
|
||||
")
|
||||
expect_compile(format-string-name-error "
|
||||
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
using namespace fmt::literals;
|
||||
fmt::print(\"{foo}\", \"bar\"_a=42);
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
" ERROR)
|
||||
endif ()
|
||||
|
||||
# Run all tests
|
||||
run_tests()
|
||||
|
62
test/compile-fp-test.cc
Normal file
62
test/compile-fp-test.cc
Normal file
@ -0,0 +1,62 @@
|
||||
// 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 defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \
|
||||
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
|
||||
defined(__cpp_constexpr_dynamic_alloc) && \
|
||||
__cpp_constexpr_dynamic_alloc >= 201907 && __cplusplus >= 202002L
|
||||
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
|
@ -5,138 +5,100 @@
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cfloat>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// Check if fmt/compile.h compiles with windows.h included before it.
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "fmt/compile.h"
|
||||
#include "gmock.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "fmt/chrono.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "mock-allocator.h"
|
||||
#include "util.h"
|
||||
|
||||
#undef ERROR
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
|
||||
// compiletime_prepared_parts_type_provider is useful only with relaxed
|
||||
// constexpr.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
template <unsigned EXPECTED_PARTS_COUNT, typename Format>
|
||||
void check_prepared_parts_type(Format format) {
|
||||
typedef fmt::detail::compiled_format_base<decltype(format)> provider;
|
||||
typedef fmt::detail::format_part<char>
|
||||
expected_parts_type[EXPECTED_PARTS_COUNT];
|
||||
static_assert(std::is_same<typename provider::parts_container,
|
||||
expected_parts_type>::value,
|
||||
"CompileTimePreparedPartsTypeProvider test failed");
|
||||
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(CompileTest, CompileTimePreparedPartsTypeProvider) {
|
||||
check_prepared_parts_type<1u>(FMT_STRING("text"));
|
||||
check_prepared_parts_type<1u>(FMT_STRING("{}"));
|
||||
check_prepared_parts_type<2u>(FMT_STRING("text{}"));
|
||||
check_prepared_parts_type<2u>(FMT_STRING("{}text"));
|
||||
check_prepared_parts_type<3u>(FMT_STRING("text{}text"));
|
||||
check_prepared_parts_type<3u>(FMT_STRING("{:{}.{}} {:{}}"));
|
||||
TEST(iterator_test, truncating_iterator) {
|
||||
char* p = nullptr;
|
||||
auto it = fmt::detail::truncating_iterator<char*>(p, 3);
|
||||
auto prev = it++;
|
||||
EXPECT_EQ(prev.base(), p);
|
||||
EXPECT_EQ(it.base(), p + 1);
|
||||
}
|
||||
|
||||
check_prepared_parts_type<3u>(FMT_STRING("{{{}}}")); // '{', 'argument', '}'
|
||||
check_prepared_parts_type<2u>(FMT_STRING("text{{")); // 'text', '{'
|
||||
check_prepared_parts_type<3u>(FMT_STRING("text{{ ")); // 'text', '{', ' '
|
||||
check_prepared_parts_type<2u>(FMT_STRING("}}text")); // '}', text
|
||||
check_prepared_parts_type<2u>(FMT_STRING("text}}text")); // 'text}', 'text'
|
||||
check_prepared_parts_type<4u>(
|
||||
FMT_STRING("text{{}}text")); // 'text', '{', '}', 'text'
|
||||
TEST(iterator_test, truncating_iterator_default_construct) {
|
||||
auto it = fmt::detail::truncating_iterator<char*>();
|
||||
EXPECT_EQ(nullptr, it.base());
|
||||
EXPECT_EQ(std::size_t{0}, it.count());
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_ranges
|
||||
TEST(iterator_test, truncating_iterator_is_output_iterator) {
|
||||
static_assert(
|
||||
std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(CompileTest, PassStringLiteralFormat) {
|
||||
const auto prepared = fmt::detail::compile<int>("test {}");
|
||||
EXPECT_EQ("test 42", fmt::format(prepared, 42));
|
||||
const auto wprepared = fmt::detail::compile<int>(L"test {}");
|
||||
EXPECT_EQ(L"test 42", fmt::format(wprepared, 42));
|
||||
TEST(iterator_test, truncating_back_inserter) {
|
||||
auto buffer = std::string();
|
||||
auto bi = std::back_inserter(buffer);
|
||||
auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
|
||||
*it++ = '4';
|
||||
*it++ = '2';
|
||||
*it++ = '1';
|
||||
EXPECT_EQ(buffer.size(), 2);
|
||||
EXPECT_EQ(buffer, "42");
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatToArrayOfChars) {
|
||||
char buffer[32] = {0};
|
||||
const auto prepared = fmt::detail::compile<int>("4{}");
|
||||
fmt::format_to(fmt::detail::make_checked(buffer, 32), prepared, 2);
|
||||
EXPECT_EQ(std::string("42"), buffer);
|
||||
wchar_t wbuffer[32] = {0};
|
||||
const auto wprepared = fmt::detail::compile<int>(L"4{}");
|
||||
fmt::format_to(fmt::detail::make_checked(wbuffer, 32), wprepared, 2);
|
||||
EXPECT_EQ(std::wstring(L"42"), wbuffer);
|
||||
TEST(compile_test, compile_fallback) {
|
||||
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
||||
// not available.
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatToIterator) {
|
||||
std::string s(2, ' ');
|
||||
const auto prepared = fmt::detail::compile<int>("4{}");
|
||||
fmt::format_to(s.begin(), prepared, 2);
|
||||
EXPECT_EQ("42", s);
|
||||
std::wstring ws(2, L' ');
|
||||
const auto wprepared = fmt::detail::compile<int>(L"4{}");
|
||||
fmt::format_to(ws.begin(), wprepared, 2);
|
||||
EXPECT_EQ(L"42", ws);
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatToN) {
|
||||
char buf[5];
|
||||
auto f = fmt::detail::compile<int>("{:10}");
|
||||
auto result = fmt::format_to_n(buf, 5, f, 42);
|
||||
EXPECT_EQ(result.size, 10);
|
||||
EXPECT_EQ(result.out, buf + 5);
|
||||
EXPECT_EQ(fmt::string_view(buf, 5), " ");
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormattedSize) {
|
||||
auto f = fmt::detail::compile<int>("{:10}");
|
||||
EXPECT_EQ(fmt::formatted_size(f, 42), 10);
|
||||
}
|
||||
|
||||
TEST(CompileTest, MultipleTypes) {
|
||||
auto f = fmt::detail::compile<int, int>("{} {}");
|
||||
EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
|
||||
}
|
||||
|
||||
struct formattable {};
|
||||
struct type_with_get {
|
||||
template <int> friend void get(type_with_get);
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<formattable> : formatter<const char*> {
|
||||
template <> struct formatter<type_with_get> : formatter<int> {
|
||||
template <typename FormatContext>
|
||||
auto format(formattable, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return formatter<const char*>::format("foo", ctx);
|
||||
auto format(type_with_get, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return formatter<int>::format(42, ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CompileTest, FormatUserDefinedType) {
|
||||
auto f = fmt::detail::compile<formattable>("{}");
|
||||
EXPECT_EQ(fmt::format(f, formattable()), "foo");
|
||||
TEST(compile_test, compile_type_with_get) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), type_with_get()));
|
||||
}
|
||||
|
||||
TEST(CompileTest, EmptyFormatString) {
|
||||
auto f = fmt::detail::compile<>("");
|
||||
EXPECT_EQ(fmt::format(f), "");
|
||||
}
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
struct test_formattable {};
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
TEST(CompileTest, FormatDefault) {
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<test_formattable> : formatter<const char*> {
|
||||
char word_spec = 'f';
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
if (it != end && (*it == 'f' || *it == 'b')) word_spec = *it++;
|
||||
if (it != end && *it != '}') throw format_error("invalid format");
|
||||
return it;
|
||||
}
|
||||
template <typename FormatContext>
|
||||
constexpr auto format(test_formattable, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<const char*>::format(word_spec == 'f' ? "foo" : "bar",
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(compile_test, format_default) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
|
||||
@ -146,21 +108,269 @@ TEST(CompileTest, FormatDefault) {
|
||||
EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), formattable()));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable()));
|
||||
auto t = std::chrono::system_clock::now();
|
||||
EXPECT_EQ(fmt::format("{}", t), fmt::format(FMT_COMPILE("{}"), t));
|
||||
# ifdef __cpp_lib_byte
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), std::byte{42}));
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatSpecs) {
|
||||
TEST(compile_test, format_wide_string) {
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
|
||||
}
|
||||
|
||||
TEST(compile_test, format_specs) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
||||
EXPECT_EQ("1.2 ms ",
|
||||
fmt::format(FMT_COMPILE("{:7.1%Q %q}"),
|
||||
std::chrono::duration<double, std::milli>(1.234)));
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatTo) {
|
||||
TEST(compile_test, dynamic_format_specs) {
|
||||
EXPECT_EQ("foo ", fmt::format(FMT_COMPILE("{:{}}"), "foo", 5));
|
||||
EXPECT_EQ(" 3.14", fmt::format(FMT_COMPILE("{:{}.{}f}"), 3.141592, 6, 2));
|
||||
EXPECT_EQ(
|
||||
"=1.234ms=",
|
||||
fmt::format(FMT_COMPILE("{:=^{}.{}}"),
|
||||
std::chrono::duration<double, std::milli>(1.234), 9, 3));
|
||||
}
|
||||
|
||||
TEST(compile_test, manual_ordering) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42));
|
||||
EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42));
|
||||
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43));
|
||||
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{1} {0}"), 43, 41));
|
||||
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {2}"), 41, 42, 43));
|
||||
EXPECT_EQ(" 41 43", fmt::format(FMT_COMPILE("{1:{2}} {0:4}"), 43, 41, 4));
|
||||
EXPECT_EQ("42 1.2 ms ",
|
||||
fmt::format(FMT_COMPILE("{0} {1:7.1%Q %q}"), 42,
|
||||
std::chrono::duration<double, std::milli>(1.234)));
|
||||
EXPECT_EQ(
|
||||
"true 42 42 foo 0x1234 foo",
|
||||
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
|
||||
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
|
||||
}
|
||||
|
||||
TEST(compile_test, named) {
|
||||
auto runtime_named_field_compiled =
|
||||
fmt::detail::compile<decltype(fmt::arg("arg", 42))>(FMT_COMPILE("{arg}"));
|
||||
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
|
||||
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",
|
||||
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
|
||||
fmt::arg("a1", "bar")));
|
||||
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{}{a1}"), fmt::arg("a0", "foo"),
|
||||
fmt::arg("a1", "bar")));
|
||||
EXPECT_EQ("foofoo", fmt::format(FMT_COMPILE("{a0}{}"), fmt::arg("a0", "foo"),
|
||||
fmt::arg("a1", "bar")));
|
||||
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{0}{a1}"), fmt::arg("a0", "foo"),
|
||||
fmt::arg("a1", "bar")));
|
||||
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{a0}{1}"), fmt::arg("a0", "foo"),
|
||||
fmt::arg("a1", "bar")));
|
||||
|
||||
EXPECT_EQ("foobar",
|
||||
fmt::format(FMT_COMPILE("{}{a1}"), "foo", fmt::arg("a1", "bar")));
|
||||
EXPECT_EQ("foobar",
|
||||
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a1", "bar"),
|
||||
fmt::arg("a2", "baz"), fmt::arg("a0", "foo")));
|
||||
EXPECT_EQ(" bar foo ",
|
||||
fmt::format(FMT_COMPILE(" {foo} {bar} "), fmt::arg("foo", "bar"),
|
||||
fmt::arg("bar", "foo")));
|
||||
|
||||
EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)),
|
||||
fmt::format_error);
|
||||
|
||||
# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
using namespace fmt::literals;
|
||||
auto statically_named_field_compiled =
|
||||
fmt::detail::compile<decltype("arg"_a = 42)>(FMT_COMPILE("{arg}"));
|
||||
static_assert(std::is_same_v<decltype(statically_named_field_compiled),
|
||||
fmt::detail::field<char, int, 0>>);
|
||||
|
||||
EXPECT_EQ("41 43",
|
||||
fmt::format(FMT_COMPILE("{a0} {a1}"), "a0"_a = 41, "a1"_a = 43));
|
||||
EXPECT_EQ("41 43",
|
||||
fmt::format(FMT_COMPILE("{a1} {a0}"), "a0"_a = 43, "a1"_a = 41));
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(compile_test, format_to) {
|
||||
char buf[8];
|
||||
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ("42", buf);
|
||||
end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ("2a", buf);
|
||||
}
|
||||
|
||||
TEST(CompileTest, TextAndArg) {
|
||||
TEST(compile_test, format_to_n) {
|
||||
constexpr auto buffer_size = 8;
|
||||
char buffer[buffer_size];
|
||||
auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42);
|
||||
*res.out = '\0';
|
||||
EXPECT_STREQ("42", buffer);
|
||||
res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42);
|
||||
*res.out = '\0';
|
||||
EXPECT_STREQ("2a", buffer);
|
||||
}
|
||||
|
||||
TEST(compile_test, formatted_size) {
|
||||
EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42));
|
||||
EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0));
|
||||
}
|
||||
|
||||
TEST(compile_test, text_and_arg) {
|
||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
|
||||
}
|
||||
|
||||
TEST(compile_test, unknown_format_fallback) {
|
||||
EXPECT_EQ(" 42 ",
|
||||
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
|
||||
|
||||
std::vector<char> v;
|
||||
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"),
|
||||
fmt::arg("name", 42));
|
||||
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size()));
|
||||
|
||||
char buffer[4];
|
||||
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
|
||||
fmt::arg("name", 42));
|
||||
EXPECT_EQ(5u, result.size);
|
||||
EXPECT_EQ(buffer + 4, result.out);
|
||||
EXPECT_EQ(" 42 ", fmt::string_view(buffer, 4));
|
||||
}
|
||||
|
||||
TEST(compile_test, empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); }
|
||||
|
||||
struct to_stringable {
|
||||
friend fmt::string_view to_string_view(to_stringable) { return {}; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<to_stringable> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const to_stringable&, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(compile_test, to_string_and_formatter) {
|
||||
fmt::format(FMT_COMPILE("{}"), to_stringable());
|
||||
}
|
||||
|
||||
TEST(compile_test, print) {
|
||||
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
|
||||
"Don't panic!");
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
|
||||
"Don't panic!");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
TEST(compile_test, compile_format_string_literal) {
|
||||
using namespace fmt::literals;
|
||||
EXPECT_EQ("", fmt::format(""_cf));
|
||||
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
|
||||
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 202002L || \
|
||||
(__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
|
||||
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, bool) {
|
||||
EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true));
|
||||
EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false));
|
||||
EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true));
|
||||
EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, integer) {
|
||||
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), 42));
|
||||
EXPECT_EQ("420", test_format<4>(FMT_COMPILE("{}"), 420));
|
||||
EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42));
|
||||
EXPECT_EQ("42 42",
|
||||
test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42}));
|
||||
|
||||
EXPECT_EQ("+42", test_format<4>(FMT_COMPILE("{:+}"), 42));
|
||||
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{:-}"), 42));
|
||||
EXPECT_EQ(" 42", test_format<4>(FMT_COMPILE("{: }"), 42));
|
||||
|
||||
EXPECT_EQ("-0042", test_format<6>(FMT_COMPILE("{:05}"), -42));
|
||||
|
||||
EXPECT_EQ("101010", test_format<7>(FMT_COMPILE("{:b}"), 42));
|
||||
EXPECT_EQ("0b101010", test_format<9>(FMT_COMPILE("{:#b}"), 42));
|
||||
EXPECT_EQ("0B101010", test_format<9>(FMT_COMPILE("{:#B}"), 42));
|
||||
EXPECT_EQ("042", test_format<4>(FMT_COMPILE("{:#o}"), 042));
|
||||
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}"), 42ll));
|
||||
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
|
||||
|
||||
EXPECT_EQ("42 ", test_format<5>(FMT_COMPILE("{:<4}"), 42));
|
||||
EXPECT_EQ(" 42", test_format<5>(FMT_COMPILE("{:>4}"), 42));
|
||||
EXPECT_EQ(" 42 ", test_format<5>(FMT_COMPILE("{:^4}"), 42));
|
||||
EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, char) {
|
||||
EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c'));
|
||||
|
||||
EXPECT_EQ("c ", test_format<4>(FMT_COMPILE("{:3}"), 'c'));
|
||||
EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c'));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, string) {
|
||||
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42"));
|
||||
EXPECT_EQ("The answer is 42",
|
||||
test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42"));
|
||||
|
||||
EXPECT_EQ("abc**", test_format<6>(FMT_COMPILE("{:*<5}"), "abc"));
|
||||
EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^6}"), "🤡"));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, combination) {
|
||||
EXPECT_EQ("420, true, answer",
|
||||
test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer"));
|
||||
|
||||
EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, custom_type) {
|
||||
EXPECT_EQ("foo", test_format<4>(FMT_COMPILE("{}"), test_formattable()));
|
||||
EXPECT_EQ("bar", test_format<4>(FMT_COMPILE("{:b}"), test_formattable()));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, multibyte_fill) {
|
||||
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
|
||||
}
|
||||
#endif
|
||||
|
1011
test/core-test.cc
1011
test/core-test.cc
File diff suppressed because it is too large
Load Diff
@ -1,58 +0,0 @@
|
||||
// Formatting library for C++ - custom argument formatter tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "gtest-extra.h"
|
||||
|
||||
// MSVC 2013 is known to be broken.
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
|
||||
// A custom argument formatter that doesn't print `-` for floating-point values
|
||||
// rounded to 0.
|
||||
class custom_arg_formatter
|
||||
: public fmt::detail::arg_formatter<fmt::format_context::iterator, char> {
|
||||
public:
|
||||
using base = fmt::detail::arg_formatter<fmt::format_context::iterator, char>;
|
||||
|
||||
custom_arg_formatter(fmt::format_context& ctx,
|
||||
fmt::format_parse_context* parse_ctx,
|
||||
fmt::format_specs* s = nullptr,
|
||||
const char* = nullptr)
|
||||
: base(ctx, parse_ctx, s) {}
|
||||
|
||||
using base::operator();
|
||||
|
||||
iterator operator()(double value) {
|
||||
// Comparing a float to 0.0 is safe.
|
||||
if (round(value * pow(10, specs()->precision)) == 0.0) value = 0;
|
||||
return base::operator()(value);
|
||||
}
|
||||
};
|
||||
|
||||
std::string custom_vformat(fmt::string_view format_str, fmt::format_args args) {
|
||||
fmt::memory_buffer buffer;
|
||||
fmt::detail::buffer<char>& base = buffer;
|
||||
// Pass custom argument formatter as a template arg to vwrite.
|
||||
fmt::vformat_to<custom_arg_formatter>(std::back_inserter(base), format_str,
|
||||
args);
|
||||
return std::string(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
std::string custom_format(const char* format_str, const Args&... args) {
|
||||
auto va = fmt::make_format_args(args...);
|
||||
return custom_vformat(format_str, va);
|
||||
}
|
||||
|
||||
TEST(CustomFormatterTest, Format) {
|
||||
EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001));
|
||||
}
|
||||
#endif
|
63
test/enforce-checks-test.cc
Normal file
63
test/enforce-checks-test.cc
Normal file
@ -0,0 +1,63 @@
|
||||
// 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 <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/ostream.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
// Exercise the API to verify that everything we expect to can compile.
|
||||
void test_format_api() {
|
||||
(void)fmt::format(FMT_STRING("{}"), 42);
|
||||
(void)fmt::format(FMT_STRING(L"{}"), 42);
|
||||
(void)fmt::format(FMT_STRING("noop"));
|
||||
|
||||
(void)fmt::to_string(42);
|
||||
(void)fmt::to_wstring(42);
|
||||
|
||||
std::vector<char> out;
|
||||
fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42);
|
||||
|
||||
char buffer[4];
|
||||
fmt::format_to_n(buffer, 3, FMT_STRING("{}"), 12345);
|
||||
|
||||
wchar_t wbuffer[4];
|
||||
fmt::format_to_n(wbuffer, 3, FMT_STRING(L"{}"), 12345);
|
||||
}
|
||||
|
||||
void test_chrono() {
|
||||
(void)fmt::format(FMT_STRING("{}"), std::chrono::seconds(42));
|
||||
(void)fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42));
|
||||
}
|
||||
|
||||
void test_text_style() {
|
||||
fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
|
||||
(void)fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"),
|
||||
"rgb(255,20,30)");
|
||||
|
||||
fmt::text_style ts = fg(fmt::rgb(255, 20, 30));
|
||||
std::string out;
|
||||
fmt::format_to(std::back_inserter(out), ts,
|
||||
FMT_STRING("rgb(255,20,30){}{}{}"), 1, 2, 3);
|
||||
}
|
||||
|
||||
void test_range() {
|
||||
std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'};
|
||||
(void)fmt::format(FMT_STRING("{}"), hello);
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_format_api();
|
||||
test_chrono();
|
||||
test_text_style();
|
||||
test_range();
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
|
||||
project(fmt-test)
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "fmt/format.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
for(int i = 0; i < argc; ++i)
|
||||
fmt::print("{}: {}\n", i, argv[i]);
|
||||
for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]);
|
||||
}
|
||||
|
855
test/format
855
test/format
@ -1,855 +0,0 @@
|
||||
// Formatting library for C++ - the standard API implementation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_FORMAT_
|
||||
#define FMT_FORMAT_
|
||||
|
||||
#include <cassert>
|
||||
#include <variant>
|
||||
#include "fmt/format.h"
|
||||
|
||||
// This implementation verifies the correctness of the standard API proposed in
|
||||
// P0645 Text Formatting and is optimized for copy-pasting from the paper, not
|
||||
// for efficiency or readability. An efficient implementation should not use
|
||||
// std::variant and should store packed argument type tags separately from
|
||||
// values in basic_format_args for small number of arguments.
|
||||
|
||||
namespace std {
|
||||
template<class T>
|
||||
constexpr bool Integral = is_integral_v<T>;
|
||||
|
||||
template <class O>
|
||||
using iter_difference_t = ptrdiff_t;
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.syn
|
||||
namespace std {
|
||||
// [format.error], class format_error
|
||||
class format_error;
|
||||
|
||||
// [format.formatter], formatter
|
||||
template<class charT> class basic_format_parse_context;
|
||||
using format_parse_context = basic_format_parse_context<char>;
|
||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||
|
||||
template<class Out, class charT> class basic_format_context;
|
||||
using format_context = basic_format_context<
|
||||
/* unspecified */ std::back_insert_iterator<fmt::detail::buffer<char>>, char>;
|
||||
using wformat_context = basic_format_context<
|
||||
/* unspecified */ std::back_insert_iterator<fmt::detail::buffer<wchar_t>>, wchar_t>;
|
||||
|
||||
template<class T, class charT = char> struct formatter {
|
||||
formatter() = delete;
|
||||
};
|
||||
|
||||
// [format.arguments], arguments
|
||||
template<class Context> class basic_format_arg;
|
||||
|
||||
template<class Visitor, class Context>
|
||||
/* see below */ auto visit_format_arg(Visitor&& vis, basic_format_arg<Context> arg);
|
||||
|
||||
template<class Context, class... Args> struct format_arg_store; // exposition only
|
||||
|
||||
template<class Context> class basic_format_args;
|
||||
using format_args = basic_format_args<format_context>;
|
||||
using wformat_args = basic_format_args<wformat_context>;
|
||||
|
||||
template<class Out, class charT>
|
||||
using format_args_t = basic_format_args<basic_format_context<Out, charT>>;
|
||||
|
||||
template<class Context = format_context, class... Args>
|
||||
format_arg_store<Context, Args...>
|
||||
make_format_args(const Args&... args);
|
||||
template<class... Args>
|
||||
format_arg_store<wformat_context, Args...>
|
||||
make_wformat_args(const Args&... args);
|
||||
|
||||
// [format.functions], formatting functions
|
||||
template<class... Args>
|
||||
string format(string_view fmt, const Args&... args);
|
||||
template<class... Args>
|
||||
wstring format(wstring_view fmt, const Args&... args);
|
||||
|
||||
string vformat(string_view fmt, format_args args);
|
||||
wstring vformat(wstring_view fmt, wformat_args args);
|
||||
|
||||
template<class Out, class... Args>
|
||||
Out format_to(Out out, string_view fmt, const Args&... args);
|
||||
template<class Out, class... Args>
|
||||
Out format_to(Out out, wstring_view fmt, const Args&... args);
|
||||
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args);
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args);
|
||||
|
||||
template<class Out>
|
||||
struct format_to_n_result {
|
||||
Out out;
|
||||
iter_difference_t<Out> size;
|
||||
};
|
||||
|
||||
template<class Out, class... Args>
|
||||
format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
|
||||
string_view fmt, const Args&... args);
|
||||
template<class Out, class... Args>
|
||||
format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
|
||||
wstring_view fmt, const Args&... args);
|
||||
|
||||
template<class... Args>
|
||||
size_t formatted_size(string_view fmt, const Args&... args);
|
||||
template<class... Args>
|
||||
size_t formatted_size(wstring_view fmt, const Args&... args);
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.error
|
||||
namespace std {
|
||||
class format_error : public runtime_error {
|
||||
public:
|
||||
explicit format_error(const string& what_arg) : runtime_error(what_arg) {}
|
||||
explicit format_error(const char* what_arg) : runtime_error(what_arg) {}
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
namespace detail {
|
||||
struct error_handler {
|
||||
// This function is intentionally not constexpr to give a compile-time error.
|
||||
void on_error(const char* message) {
|
||||
throw std::format_error(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.parse_context
|
||||
namespace std {
|
||||
template<class charT>
|
||||
class basic_format_parse_context {
|
||||
public:
|
||||
using char_type = charT;
|
||||
using const_iterator = typename basic_string_view<charT>::const_iterator;
|
||||
using iterator = const_iterator;
|
||||
|
||||
private:
|
||||
iterator begin_; // exposition only
|
||||
iterator end_; // exposition only
|
||||
enum indexing { unknown, manual, automatic }; // exposition only
|
||||
indexing indexing_; // exposition only
|
||||
size_t next_arg_id_; // exposition only
|
||||
size_t num_args_; // exposition only
|
||||
|
||||
public:
|
||||
explicit constexpr basic_format_parse_context(basic_string_view<charT> fmt,
|
||||
size_t num_args = 0) noexcept;
|
||||
basic_format_parse_context(const basic_format_parse_context&) = delete;
|
||||
basic_format_parse_context& operator=(const basic_format_parse_context&) = delete;
|
||||
|
||||
constexpr const_iterator begin() const noexcept;
|
||||
constexpr const_iterator end() const noexcept;
|
||||
constexpr void advance_to(const_iterator it);
|
||||
|
||||
constexpr size_t next_arg_id();
|
||||
constexpr void check_arg_id(size_t id);
|
||||
|
||||
// Implementation detail:
|
||||
constexpr void check_arg_id(fmt::string_view) {}
|
||||
detail::error_handler error_handler() const { return {}; }
|
||||
void on_error(const char* msg) { error_handler().on_error(msg); }
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template<class charT>
|
||||
/* explicit */ constexpr basic_format_parse_context<charT>::
|
||||
basic_format_parse_context(basic_string_view<charT> fmt,
|
||||
size_t num_args) noexcept
|
||||
: begin_(fmt.begin()), end_(fmt.end()), indexing_(unknown), next_arg_id_(0), num_args_(num_args) {}
|
||||
|
||||
template<class charT>
|
||||
constexpr typename basic_format_parse_context<charT>::const_iterator basic_format_parse_context<charT>::begin() const noexcept { return begin_; }
|
||||
|
||||
template<class charT>
|
||||
constexpr typename basic_format_parse_context<charT>::const_iterator basic_format_parse_context<charT>::end() const noexcept { return end_; }
|
||||
|
||||
template<class charT>
|
||||
constexpr void basic_format_parse_context<charT>::advance_to(typename basic_format_parse_context<charT>::iterator it) { begin_ = it; }
|
||||
|
||||
template<class charT>
|
||||
constexpr size_t basic_format_parse_context<charT>::next_arg_id() {
|
||||
if (indexing_ == manual)
|
||||
throw format_error("manual to automatic indexing");
|
||||
if (indexing_ == unknown)
|
||||
indexing_ = automatic;
|
||||
return next_arg_id_++;
|
||||
}
|
||||
|
||||
template<class charT>
|
||||
constexpr void basic_format_parse_context<charT>::check_arg_id(size_t id) {
|
||||
// clang doesn't support __builtin_is_constant_evaluated yet
|
||||
//if (!(!__builtin_is_constant_evaluated() || id < num_args_))
|
||||
// throw format_error(invalid index is out of range");
|
||||
if (indexing_ == automatic)
|
||||
throw format_error("automatic to manual indexing");
|
||||
if (indexing_ == unknown)
|
||||
indexing_ = manual;
|
||||
}
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.context
|
||||
namespace std {
|
||||
template<class Out, class charT>
|
||||
class basic_format_context {
|
||||
basic_format_args<basic_format_context> args_; // exposition only
|
||||
Out out_; // exposition only
|
||||
|
||||
public:
|
||||
using iterator = Out;
|
||||
using char_type = charT;
|
||||
template<class T> using formatter_type = formatter<T, charT>;
|
||||
|
||||
basic_format_arg<basic_format_context> arg(size_t id) const;
|
||||
|
||||
iterator out();
|
||||
void advance_to(iterator it);
|
||||
|
||||
// Implementation details:
|
||||
using format_arg = basic_format_arg<basic_format_context>;
|
||||
basic_format_context(Out out, basic_format_args<basic_format_context> args, fmt::detail::locale_ref)
|
||||
: args_(args), out_(out) {}
|
||||
detail::error_handler error_handler() const { return {}; }
|
||||
basic_format_arg<basic_format_context> arg(fmt::basic_string_view<charT>) const {
|
||||
return {}; // unused: named arguments are not supported yet
|
||||
}
|
||||
void on_error(const char* msg) { error_handler().on_error(msg); }
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template<class O, class charT>
|
||||
basic_format_arg<basic_format_context<O, charT>> basic_format_context<O, charT>::arg(size_t id) const { return args_.get(id); }
|
||||
|
||||
template<class O, class charT>
|
||||
typename basic_format_context<O, charT>::iterator basic_format_context<O, charT>::out() { return out_; }
|
||||
|
||||
template<class O, class charT>
|
||||
void basic_format_context<O, charT>::advance_to(typename basic_format_context<O, charT>::iterator it) { out_ = it; }
|
||||
}
|
||||
|
||||
namespace std {
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
constexpr bool is_standard_integer_v =
|
||||
std::is_same_v<T, signed char> ||
|
||||
std::is_same_v<T, short int> ||
|
||||
std::is_same_v<T, int> ||
|
||||
std::is_same_v<T, long int> ||
|
||||
std::is_same_v<T, long long int>;
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_standard_unsigned_integer_v =
|
||||
std::is_same_v<T, unsigned char> ||
|
||||
std::is_same_v<T, unsigned short int> ||
|
||||
std::is_same_v<T, unsigned int> ||
|
||||
std::is_same_v<T, unsigned long int> ||
|
||||
std::is_same_v<T, unsigned long long int>;
|
||||
|
||||
template <typename T, typename Char> struct formatter;
|
||||
}
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.arg
|
||||
namespace std {
|
||||
template<class Context>
|
||||
class basic_format_arg {
|
||||
public:
|
||||
class handle;
|
||||
|
||||
private:
|
||||
using char_type = typename Context::char_type; // exposition only
|
||||
|
||||
variant<monostate, bool, char_type,
|
||||
int, unsigned int, long long int, unsigned long long int,
|
||||
double, long double,
|
||||
const char_type*, basic_string_view<char_type>,
|
||||
const void*, handle> value; // exposition only
|
||||
|
||||
template<typename T,
|
||||
typename = enable_if_t<
|
||||
std::is_same_v<T, bool> ||
|
||||
std::is_same_v<T, char_type> ||
|
||||
(std::is_same_v<T, char> && std::is_same_v<char_type, wchar_t>) ||
|
||||
detail::is_standard_integer_v<T> ||
|
||||
detail::is_standard_unsigned_integer_v<T> ||
|
||||
sizeof(typename Context::template formatter_type<T>().format(declval<const T&>(), declval<Context&>())) != 0
|
||||
>> explicit basic_format_arg(const T& v) noexcept; // exposition only
|
||||
explicit basic_format_arg(float n) noexcept; // exposition only
|
||||
explicit basic_format_arg(double n) noexcept; // exposition only
|
||||
explicit basic_format_arg(long double n) noexcept; // exposition only
|
||||
explicit basic_format_arg(const char_type* s); // exposition only
|
||||
|
||||
template<class traits>
|
||||
explicit basic_format_arg(
|
||||
basic_string_view<char_type, traits> s) noexcept; // exposition only
|
||||
|
||||
template<class traits, class Allocator>
|
||||
explicit basic_format_arg(
|
||||
const basic_string<char_type, traits, Allocator>& s) noexcept; // exposition only
|
||||
|
||||
explicit basic_format_arg(nullptr_t) noexcept; // exposition only
|
||||
|
||||
template<class T, typename = enable_if_t<is_void_v<T>>>
|
||||
explicit basic_format_arg(const T* p) noexcept; // exposition only
|
||||
|
||||
// Fails due to a bug in clang
|
||||
//template<class Visitor, class Ctx>
|
||||
// friend auto visit_format_arg(Visitor&& vis,
|
||||
// basic_format_arg<Ctx> arg); // exposition only
|
||||
|
||||
friend auto get_value(basic_format_arg arg) {
|
||||
return arg.value;
|
||||
}
|
||||
|
||||
template <typename T, typename Char> friend struct detail::formatter;
|
||||
|
||||
template<class Ctx, class... Args>
|
||||
friend format_arg_store<Ctx, Args...>
|
||||
make_format_args(const Args&... args); // exposition only
|
||||
|
||||
public:
|
||||
basic_format_arg() noexcept;
|
||||
|
||||
explicit operator bool() const noexcept;
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template<class Context>
|
||||
basic_format_arg<Context>::basic_format_arg() noexcept {}
|
||||
|
||||
template<class Context>
|
||||
template<class T, typename> /* explicit */ basic_format_arg<Context>::basic_format_arg(const T& v) noexcept {
|
||||
if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, char_type>)
|
||||
value = v;
|
||||
else if constexpr (std::is_same_v<T, char> && std::is_same_v<char_type, wchar_t>)
|
||||
value = static_cast<wchar_t>(v);
|
||||
else if constexpr (detail::is_standard_integer_v<T> && sizeof(T) <= sizeof(int))
|
||||
value = static_cast<int>(v);
|
||||
else if constexpr (detail::is_standard_unsigned_integer_v<T> && sizeof(T) <= sizeof(unsigned))
|
||||
value = static_cast<unsigned>(v);
|
||||
else if constexpr (detail::is_standard_integer_v<T>)
|
||||
value = static_cast<long long int>(v);
|
||||
else if constexpr (detail::is_standard_unsigned_integer_v<T>)
|
||||
value = static_cast<unsigned long long int>(v);
|
||||
else if constexpr (sizeof(typename Context::template formatter_type<T>().format(declval<const T&>(), declval<Context&>())) != 0)
|
||||
value = handle(v);
|
||||
}
|
||||
|
||||
template<class Context>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(float n) noexcept
|
||||
: value(static_cast<double>(n)) {}
|
||||
|
||||
template<class Context>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(double n) noexcept
|
||||
: value(n) {}
|
||||
|
||||
template<class Context>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(long double n) noexcept
|
||||
: value(n) {}
|
||||
|
||||
template<class Context>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(const typename basic_format_arg<Context>::char_type* s)
|
||||
: value(s) {
|
||||
assert(s != nullptr);
|
||||
}
|
||||
|
||||
template<class Context>
|
||||
template<class traits>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(basic_string_view<char_type, traits> s) noexcept
|
||||
: value(s) {}
|
||||
|
||||
template<class Context>
|
||||
template<class traits, class Allocator>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(
|
||||
const basic_string<char_type, traits, Allocator>& s) noexcept
|
||||
: value(basic_string_view<char_type>(s.data(), s.size())) {}
|
||||
|
||||
|
||||
template<class Context>
|
||||
/* explicit */ basic_format_arg<Context>::basic_format_arg(nullptr_t) noexcept
|
||||
: value(static_cast<const void*>(nullptr)) {}
|
||||
|
||||
template<class Context>
|
||||
template<class T, typename> /* explicit */ basic_format_arg<Context>::basic_format_arg(const T* p) noexcept
|
||||
: value(p) {}
|
||||
|
||||
template<class Context>
|
||||
/* explicit */ basic_format_arg<Context>::operator bool() const noexcept {
|
||||
return !holds_alternative<monostate>(value);
|
||||
}
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template<class Context>
|
||||
class basic_format_arg<Context>::handle {
|
||||
const void* ptr_; // exposition only
|
||||
void (*format_)(basic_format_parse_context<char_type>&,
|
||||
Context&, const void*); // exposition only
|
||||
|
||||
template<class T> explicit handle(const T& val) noexcept; // exposition only
|
||||
|
||||
friend class basic_format_arg<Context>; // exposition only
|
||||
|
||||
public:
|
||||
void format(basic_format_parse_context<char_type>&, Context& ctx) const;
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template<class Context>
|
||||
template<class T> /* explicit */ basic_format_arg<Context>::handle::handle(const T& val) noexcept
|
||||
: ptr_(&val), format_([](basic_format_parse_context<char_type>& parse_ctx, Context& format_ctx, const void* ptr) {
|
||||
typename Context::template formatter_type<T> f;
|
||||
parse_ctx.advance_to(f.parse(parse_ctx));
|
||||
format_ctx.advance_to(f.format(*static_cast<const T*>(ptr), format_ctx));
|
||||
}) {}
|
||||
|
||||
template<class Context>
|
||||
void basic_format_arg<Context>::handle::format(basic_format_parse_context<char_type>& parse_ctx, Context& format_ctx) const {
|
||||
format_(parse_ctx, format_ctx, ptr_);
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.visit
|
||||
template<class Visitor, class Context>
|
||||
auto visit_format_arg(Visitor&& vis, basic_format_arg<Context> arg) {
|
||||
return visit(vis, get_value(arg));
|
||||
}
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.store
|
||||
namespace std {
|
||||
template<class Context, class... Args>
|
||||
struct format_arg_store { // exposition only
|
||||
array<basic_format_arg<Context>, sizeof...(Args)> args;
|
||||
};
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.basic_args
|
||||
namespace std {
|
||||
template<class Context>
|
||||
class basic_format_args {
|
||||
size_t size_; // exposition only
|
||||
const basic_format_arg<Context>* data_; // exposition only
|
||||
|
||||
public:
|
||||
basic_format_args() noexcept;
|
||||
|
||||
template<class... Args>
|
||||
basic_format_args(const format_arg_store<Context, Args...>& store) noexcept;
|
||||
|
||||
basic_format_arg<Context> get(size_t i) const noexcept;
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template<class Context>
|
||||
basic_format_args<Context>::basic_format_args() noexcept : size_(0) {}
|
||||
|
||||
template<class Context>
|
||||
template<class... Args>
|
||||
basic_format_args<Context>::basic_format_args(const format_arg_store<Context, Args...>& store) noexcept
|
||||
: size_(sizeof...(Args)), data_(store.args.data()) {}
|
||||
|
||||
template<class Context>
|
||||
basic_format_arg<Context> basic_format_args<Context>::get(size_t i) const noexcept {
|
||||
return i < size_ ? data_[i] : basic_format_arg<Context>();
|
||||
}
|
||||
}
|
||||
|
||||
namespace std {
|
||||
// https://fmt.dev/Text%20Formatting.html#format.make_args
|
||||
template<class Context /*= format_context*/, class... Args>
|
||||
format_arg_store<Context, Args...> make_format_args(const Args&... args) {
|
||||
return {basic_format_arg<Context>(args)...};
|
||||
}
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.make_wargs
|
||||
template<class... Args>
|
||||
format_arg_store<wformat_context, Args...> make_wformat_args(const Args&... args) {
|
||||
return make_format_args<wformat_context>(args...);
|
||||
}
|
||||
}
|
||||
|
||||
namespace std {
|
||||
namespace detail {
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
class arg_formatter
|
||||
: public fmt::detail::arg_formatter_base<OutputIt, Char, error_handler> {
|
||||
private:
|
||||
using char_type = Char;
|
||||
using base = fmt::detail::arg_formatter_base<OutputIt, Char, error_handler>;
|
||||
using format_context = std::basic_format_context<OutputIt, Char>;
|
||||
using parse_context = basic_format_parse_context<Char>;
|
||||
|
||||
parse_context* parse_ctx_;
|
||||
format_context& ctx_;
|
||||
|
||||
public:
|
||||
using iterator = OutputIt;
|
||||
using format_specs = typename base::format_specs;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*ctx* is a reference to the formatting context,
|
||||
*spec* contains format specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
arg_formatter(format_context& ctx, parse_context* parse_ctx = nullptr, fmt::format_specs* spec = nullptr)
|
||||
: base(ctx.out(), spec, {}), parse_ctx_(parse_ctx), ctx_(ctx) {}
|
||||
|
||||
using base::operator();
|
||||
|
||||
/** Formats an argument of a user-defined type. */
|
||||
iterator operator()(typename std::basic_format_arg<format_context>::handle handle) {
|
||||
handle.format(*parse_ctx_, ctx_);
|
||||
return this->out();
|
||||
}
|
||||
|
||||
iterator operator()(monostate) {
|
||||
throw format_error("");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
inline fmt::detail::type get_type(basic_format_arg<Context> arg) {
|
||||
return visit_format_arg([&] (auto val) {
|
||||
using char_type = typename Context::char_type;
|
||||
using T = decltype(val);
|
||||
if (std::is_same_v<T, monostate>)
|
||||
return fmt::detail::type::none_type;
|
||||
if (std::is_same_v<T, bool>)
|
||||
return fmt::detail::type::bool_type;
|
||||
if (std::is_same_v<T, char_type>)
|
||||
return fmt::detail::type::char_type;
|
||||
if (std::is_same_v<T, int>)
|
||||
return fmt::detail::type::int_type;
|
||||
if (std::is_same_v<T, unsigned int>)
|
||||
return fmt::detail::type::uint_type;
|
||||
if (std::is_same_v<T, long long int>)
|
||||
return fmt::detail::type::long_long_type;
|
||||
if (std::is_same_v<T, unsigned long long int>)
|
||||
return fmt::detail::type::ulong_long_type;
|
||||
if (std::is_same_v<T, double>)
|
||||
return fmt::detail::type::double_type;
|
||||
if (std::is_same_v<T, long double>)
|
||||
return fmt::detail::type::long_double_type;
|
||||
if (std::is_same_v<T, const char_type*>)
|
||||
return fmt::detail::type::cstring_type;
|
||||
if (std::is_same_v<T, basic_string_view<char_type>>)
|
||||
return fmt::detail::type::string_type;
|
||||
if (std::is_same_v<T, const void*>)
|
||||
return fmt::detail::type::pointer_type;
|
||||
assert(get_value(arg).index() == 12);
|
||||
return fmt::detail::type::custom_type;
|
||||
}, arg);
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
class custom_formatter {
|
||||
private:
|
||||
using parse_context = basic_format_parse_context<typename Context::char_type>;
|
||||
parse_context& parse_ctx_;
|
||||
Context& format_ctx_;
|
||||
|
||||
public:
|
||||
custom_formatter(parse_context& parse_ctx, Context& ctx) : parse_ctx_(parse_ctx), format_ctx_(ctx) {}
|
||||
|
||||
bool operator()(typename basic_format_arg<Context>::handle h) const {
|
||||
h.format(parse_ctx_, format_ctx_);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> bool operator()(T) const { return false; }
|
||||
};
|
||||
|
||||
template <typename ArgFormatter, typename Char, typename Context>
|
||||
struct format_handler : detail::error_handler {
|
||||
using iterator = typename ArgFormatter::iterator;
|
||||
|
||||
format_handler(iterator out, basic_string_view<Char> str,
|
||||
basic_format_args<Context> format_args,
|
||||
fmt::detail::locale_ref loc)
|
||||
: parse_ctx(str), context(out, format_args, loc) {}
|
||||
|
||||
void on_text(const Char* begin, const Char* end) {
|
||||
auto size = fmt::detail::to_unsigned(end - begin);
|
||||
auto out = context.out();
|
||||
auto&& it = fmt::detail::reserve(out, size);
|
||||
it = std::copy_n(begin, size, it);
|
||||
context.advance_to(out);
|
||||
}
|
||||
|
||||
int on_arg_id() { return parse_ctx.next_arg_id(); }
|
||||
int on_arg_id(unsigned id) { return parse_ctx.check_arg_id(id), id; }
|
||||
int on_arg_id(fmt::basic_string_view<Char>) { return 0; }
|
||||
|
||||
void on_replacement_field(int id, const Char* p) {
|
||||
auto arg = context.arg(id);
|
||||
parse_ctx.advance_to(parse_ctx.begin() + (p - &*parse_ctx.begin()));
|
||||
custom_formatter<Context> f(parse_ctx, context);
|
||||
if (!visit_format_arg(f, arg))
|
||||
context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx), arg));
|
||||
}
|
||||
|
||||
const Char* on_format_specs(int id, const Char* begin, const Char* end) {
|
||||
auto arg = context.arg(id);
|
||||
parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin()));
|
||||
custom_formatter<Context> f(parse_ctx, context);
|
||||
if (visit_format_arg(f, arg)) return &*parse_ctx.begin();
|
||||
fmt::basic_format_specs<Char> specs;
|
||||
using fmt::detail::specs_handler;
|
||||
using parse_context = basic_format_parse_context<Char>;
|
||||
fmt::detail::specs_checker<specs_handler<parse_context, Context>> handler(
|
||||
specs_handler<parse_context, Context>(specs, parse_ctx, context), get_type(arg));
|
||||
begin = parse_format_specs(begin, end, handler);
|
||||
if (begin == end || *begin != '}') on_error("missing '}' in format string");
|
||||
parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin()));
|
||||
context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx, &specs), arg));
|
||||
return begin;
|
||||
}
|
||||
|
||||
basic_format_parse_context<Char> parse_ctx;
|
||||
Context context;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter {
|
||||
// Parses format specifiers stopping either at the end of the range or at the
|
||||
// terminating '}'.
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext& ctx) {
|
||||
namespace detail = fmt::detail;
|
||||
typedef detail::dynamic_specs_handler<ParseContext> handler_type;
|
||||
auto type = detail::mapped_type_constant<T, fmt::buffer_context<Char>>::value;
|
||||
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
|
||||
type);
|
||||
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
|
||||
auto type_spec = specs_.type;
|
||||
auto eh = ctx.error_handler();
|
||||
switch (type) {
|
||||
case detail::type::none_type:
|
||||
FMT_ASSERT(false, "invalid argument type");
|
||||
break;
|
||||
case detail::type::int_type:
|
||||
case detail::type::uint_type:
|
||||
case detail::type::long_long_type:
|
||||
case detail::type::ulong_long_type:
|
||||
case detail::type::bool_type:
|
||||
handle_int_type_spec(type_spec,
|
||||
detail::int_type_checker<decltype(eh)>(eh));
|
||||
break;
|
||||
case detail::type::char_type:
|
||||
handle_char_specs(
|
||||
&specs_, detail::char_specs_checker<decltype(eh)>(type_spec, eh));
|
||||
break;
|
||||
case detail::type::double_type:
|
||||
case detail::type::long_double_type:
|
||||
detail::parse_float_type_spec(specs_, eh);
|
||||
break;
|
||||
case detail::type::cstring_type:
|
||||
detail::handle_cstring_type_spec(
|
||||
type_spec, detail::cstring_type_checker<decltype(eh)>(eh));
|
||||
break;
|
||||
case detail::type::string_type:
|
||||
detail::check_string_type_spec(type_spec, eh);
|
||||
break;
|
||||
case detail::type::pointer_type:
|
||||
detail::check_pointer_type_spec(type_spec, eh);
|
||||
break;
|
||||
case detail::type::custom_type:
|
||||
// Custom format specifiers should be checked in parse functions of
|
||||
// formatter specializations.
|
||||
break;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
fmt::detail::handle_dynamic_spec<fmt::detail::width_checker>(
|
||||
specs_.width, specs_.width_ref, ctx);
|
||||
fmt::detail::handle_dynamic_spec<fmt::detail::precision_checker>(
|
||||
specs_.precision, specs_.precision_ref, ctx);
|
||||
using af = arg_formatter<typename FormatContext::iterator,
|
||||
typename FormatContext::char_type>;
|
||||
return visit_format_arg(af(ctx, nullptr, &specs_),
|
||||
basic_format_arg<FormatContext>(val));
|
||||
}
|
||||
|
||||
private:
|
||||
fmt::detail::dynamic_format_specs<Char> specs_;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// https://fmt.dev/Text%20Formatting.html#format.functions
|
||||
template<class... Args>
|
||||
string format(string_view fmt, const Args&... args) {
|
||||
return vformat(fmt, make_format_args(args...));
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
wstring format(wstring_view fmt, const Args&... args) {
|
||||
return vformat(fmt, make_wformat_args(args...));
|
||||
}
|
||||
|
||||
string vformat(string_view fmt, format_args args) {
|
||||
fmt::memory_buffer mbuf;
|
||||
fmt::detail::buffer<char>& buf = mbuf;
|
||||
using af = detail::arg_formatter<fmt::format_context::iterator, char>;
|
||||
detail::format_handler<af, char, format_context>
|
||||
h(std::back_inserter(buf), fmt, args, {});
|
||||
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
|
||||
return to_string(mbuf);
|
||||
}
|
||||
|
||||
wstring vformat(wstring_view fmt, wformat_args args);
|
||||
|
||||
template<class Out, class... Args>
|
||||
Out format_to(Out out, string_view fmt, const Args&... args) {
|
||||
using context = basic_format_context<Out, decltype(fmt)::value_type>;
|
||||
return vformat_to(out, fmt, make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template<class Out, class... Args>
|
||||
Out format_to(Out out, wstring_view fmt, const Args&... args) {
|
||||
using context = basic_format_context<Out, decltype(fmt)::value_type>;
|
||||
return vformat_to(out, fmt, make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args) {
|
||||
using af = detail::arg_formatter<Out, char>;
|
||||
detail::format_handler<af, char, basic_format_context<Out, char>>
|
||||
h(out, fmt, args, {});
|
||||
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
|
||||
return h.context.out();
|
||||
}
|
||||
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args);
|
||||
|
||||
template<class Out, class... Args>
|
||||
format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
|
||||
string_view fmt, const Args&... args);
|
||||
template<class Out, class... Args>
|
||||
format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
|
||||
wstring_view fmt, const Args&... args);
|
||||
|
||||
template<class... Args>
|
||||
size_t formatted_size(string_view fmt, const Args&... args);
|
||||
template<class... Args>
|
||||
size_t formatted_size(wstring_view fmt, const Args&... args);
|
||||
|
||||
#define charT char
|
||||
|
||||
template<> struct formatter<charT, charT> : detail::formatter<charT, charT> {};
|
||||
|
||||
template<> struct formatter<char, wchar_t>;
|
||||
|
||||
template<> struct formatter<charT*, charT> : detail::formatter<const charT*, charT> {};
|
||||
|
||||
template<> struct formatter<const charT*, charT> : detail::formatter<const charT*, charT> {};
|
||||
|
||||
template<size_t N> struct formatter<const charT[N], charT>
|
||||
: detail::formatter<std::basic_string_view<charT>, charT> {};
|
||||
|
||||
template<class traits, class Allocator>
|
||||
struct formatter<basic_string<charT, traits, Allocator>, charT>
|
||||
: detail::formatter<std::basic_string_view<charT>, charT> {};
|
||||
|
||||
template<class traits>
|
||||
struct formatter<basic_string_view<charT, traits>, charT>
|
||||
: detail::formatter<std::basic_string_view<charT>, charT> {};
|
||||
|
||||
template <> struct formatter<nullptr_t, charT> : detail::formatter<const void*, charT> {};
|
||||
template <> struct formatter<void*, charT> : detail::formatter<const void*, charT> {};
|
||||
template <> struct formatter<const void*, charT> : detail::formatter<const void*, charT> {};
|
||||
template <> struct formatter<bool, charT> : detail::formatter<bool, charT> {};
|
||||
|
||||
template <> struct formatter<signed char, charT> : detail::formatter<int, charT> {};
|
||||
template <> struct formatter<short, charT> : detail::formatter<int, charT> {};
|
||||
template <> struct formatter<int, charT> : detail::formatter<int, charT> {};
|
||||
template <> struct formatter<long, charT>
|
||||
: detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), int, long long>, charT> {};
|
||||
template <> struct formatter<long long, charT> : detail::formatter<long long, charT> {};
|
||||
template <> struct formatter<unsigned char, charT> : detail::formatter<unsigned int, charT> {};
|
||||
template <> struct formatter<unsigned short, charT> : detail::formatter<unsigned int, charT> {};
|
||||
template <> struct formatter<unsigned int, charT> : detail::formatter<unsigned int, charT> {};
|
||||
template <> struct formatter<unsigned long, charT>
|
||||
: detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), unsigned, unsigned long long>, charT> {};
|
||||
template <> struct formatter<unsigned long long, charT> : detail::formatter<unsigned long long, charT> {};
|
||||
|
||||
template <> struct formatter<float, charT> : detail::formatter<double, charT> {};
|
||||
template <> struct formatter<double, charT> : detail::formatter<double, charT> {};
|
||||
template <> struct formatter<long double, charT> : detail::formatter<long double, charT> {};
|
||||
|
||||
#undef charT
|
||||
|
||||
#define charT wchar_t
|
||||
|
||||
template<> struct formatter<charT, charT> : detail::formatter<charT, charT> {};
|
||||
|
||||
template<> struct formatter<char, wchar_t> : detail::formatter<charT, charT> {};
|
||||
|
||||
template<> struct formatter<charT*, charT> : detail::formatter<const charT*, charT> {};
|
||||
|
||||
template<> struct formatter<const charT*, charT> : detail::formatter<const charT*, charT> {};
|
||||
|
||||
template<size_t N> struct formatter<const charT[N], charT>
|
||||
: detail::formatter<std::basic_string_view<charT>, charT> {};
|
||||
|
||||
template<class traits, class Allocator>
|
||||
struct formatter<std::basic_string<charT, traits, Allocator>, charT>
|
||||
: detail::formatter<std::basic_string_view<charT>, charT> {};
|
||||
|
||||
template<class traits>
|
||||
struct formatter<std::basic_string_view<charT, traits>, charT>
|
||||
: detail::formatter<std::basic_string_view<charT>, charT> {};
|
||||
|
||||
template <> struct formatter<nullptr_t, charT> : detail::formatter<const void*, charT> {};
|
||||
template <> struct formatter<void*, charT> : detail::formatter<const void*, charT> {};
|
||||
template <> struct formatter<const void*, charT> : detail::formatter<const void*, charT> {};
|
||||
template <> struct formatter<bool, charT> : detail::formatter<bool, charT> {};
|
||||
|
||||
template <> struct formatter<signed char, charT> : detail::formatter<int, charT> {};
|
||||
template <> struct formatter<short, charT> : detail::formatter<int, charT> {};
|
||||
template <> struct formatter<int, charT> : detail::formatter<int, charT> {};
|
||||
template <> struct formatter<long, charT>
|
||||
: detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), int, long long>, charT> {};
|
||||
template <> struct formatter<long long, charT> : detail::formatter<long long, charT> {};
|
||||
template <> struct formatter<unsigned char, charT> : detail::formatter<unsigned int, charT> {};
|
||||
template <> struct formatter<unsigned short, charT> : detail::formatter<unsigned int, charT> {};
|
||||
template <> struct formatter<unsigned int, charT> : detail::formatter<unsigned int, charT> {};
|
||||
template <> struct formatter<unsigned long, charT>
|
||||
: detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), unsigned, unsigned long long>, charT> {};
|
||||
template <> struct formatter<unsigned long long, charT> : detail::formatter<unsigned long long, charT> {};
|
||||
|
||||
template <> struct formatter<float, charT> : detail::formatter<double, charT> {};
|
||||
template <> struct formatter<double, charT> : detail::formatter<double, charT> {};
|
||||
template <> struct formatter<long double, charT> : detail::formatter<long double, charT> {};
|
||||
|
||||
#undef charT
|
||||
|
||||
template<> struct formatter<const wchar_t, char> {
|
||||
formatter() = delete;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // FMT_FORMAT_
|
@ -1,6 +0,0 @@
|
||||
// Copyright (c) 2020 Vladimir Solontsov
|
||||
// SPDX-License-Identifier: MIT Licence
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "gtest-extra.h"
|
@ -5,21 +5,16 @@
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#define FMT_NOEXCEPT
|
||||
#undef FMT_SHARED
|
||||
#include "test-assert.h"
|
||||
|
||||
// Include format.cc instead of format.h to test implementation.
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/format.cc"
|
||||
#include "fmt/printf.h"
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
// clang-format off
|
||||
#include "test-assert.h"
|
||||
// clang-format on
|
||||
|
||||
#undef max
|
||||
#include "fmt/format.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "util.h"
|
||||
|
||||
using fmt::detail::bigint;
|
||||
using fmt::detail::fp;
|
||||
@ -28,13 +23,13 @@ using fmt::detail::max_value;
|
||||
static_assert(!std::is_copy_constructible<bigint>::value, "");
|
||||
static_assert(!std::is_copy_assignable<bigint>::value, "");
|
||||
|
||||
TEST(BigIntTest, Construct) {
|
||||
TEST(bigint_test, construct) {
|
||||
EXPECT_EQ("", fmt::format("{}", bigint()));
|
||||
EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
|
||||
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Compare) {
|
||||
TEST(bigint_test, compare) {
|
||||
bigint n1(42);
|
||||
bigint n2(42);
|
||||
EXPECT_EQ(compare(n1, n2), 0);
|
||||
@ -48,7 +43,7 @@ TEST(BigIntTest, Compare) {
|
||||
EXPECT_GT(compare(n4, n2), 0);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, AddCompare) {
|
||||
TEST(bigint_test, add_compare) {
|
||||
EXPECT_LT(
|
||||
add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
|
||||
EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
|
||||
@ -74,7 +69,7 @@ TEST(BigIntTest, AddCompare) {
|
||||
0);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, ShiftLeft) {
|
||||
TEST(bigint_test, shift_left) {
|
||||
bigint n(0x42);
|
||||
n <<= 0;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
@ -84,7 +79,7 @@ TEST(BigIntTest, ShiftLeft) {
|
||||
EXPECT_EQ("108000000", fmt::format("{}", n));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Multiply) {
|
||||
TEST(bigint_test, multiply) {
|
||||
bigint n(0x42);
|
||||
EXPECT_THROW(n *= 0, assertion_failure);
|
||||
n *= 1;
|
||||
@ -101,7 +96,7 @@ TEST(BigIntTest, Multiply) {
|
||||
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Accumulator) {
|
||||
TEST(bigint_test, accumulator) {
|
||||
fmt::detail::accumulator acc;
|
||||
EXPECT_EQ(acc.lower, 0);
|
||||
EXPECT_EQ(acc.upper, 0);
|
||||
@ -110,7 +105,7 @@ TEST(BigIntTest, Accumulator) {
|
||||
EXPECT_EQ(static_cast<uint32_t>(acc), 34);
|
||||
acc += 56;
|
||||
EXPECT_EQ(acc.lower, 90);
|
||||
acc += fmt::detail::max_value<uint64_t>();
|
||||
acc += max_value<uint64_t>();
|
||||
EXPECT_EQ(acc.upper, 13);
|
||||
EXPECT_EQ(acc.lower, 89);
|
||||
acc >>= 32;
|
||||
@ -118,7 +113,7 @@ TEST(BigIntTest, Accumulator) {
|
||||
EXPECT_EQ(acc.lower, 13 * 0x100000000);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Square) {
|
||||
TEST(bigint_test, square) {
|
||||
bigint n0(0);
|
||||
n0.square();
|
||||
EXPECT_EQ("0", fmt::format("{}", n0));
|
||||
@ -136,18 +131,18 @@ TEST(BigIntTest, Square) {
|
||||
EXPECT_EQ("2540be400", fmt::format("{}", n4));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignZeroDivisor) {
|
||||
TEST(bigint_test, divmod_assign_zero_divisor) {
|
||||
bigint zero(0);
|
||||
EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
|
||||
EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignSelf) {
|
||||
TEST(bigint_test, divmod_assign_self) {
|
||||
bigint n(100);
|
||||
EXPECT_THROW(n.divmod_assign(n), assertion_failure);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignUnaligned) {
|
||||
TEST(bigint_test, divmod_assign_unaligned) {
|
||||
// (42 << 340) / pow(10, 100):
|
||||
bigint n1(42);
|
||||
n1 <<= 340;
|
||||
@ -159,7 +154,7 @@ TEST(BigIntTest, DivModAssignUnaligned) {
|
||||
fmt::format("{}", n1));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssign) {
|
||||
TEST(bigint_test, divmod_assign) {
|
||||
// 100 / 10:
|
||||
bigint n1(100);
|
||||
int result = n1.divmod_assign(bigint(10));
|
||||
@ -186,70 +181,20 @@ template <bool is_iec559> void run_double_tests() {
|
||||
template <> void run_double_tests<true>() {
|
||||
// Construct from double.
|
||||
EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
|
||||
|
||||
// Compute boundaries:
|
||||
fp value;
|
||||
// Normalized & not power of 2 - equidistant boundaries:
|
||||
auto b = value.assign_with_boundaries(1.23);
|
||||
EXPECT_EQ(value, fp(0x0013ae147ae147ae, -52));
|
||||
EXPECT_EQ(b.lower, 0x9d70a3d70a3d6c00);
|
||||
EXPECT_EQ(b.upper, 0x9d70a3d70a3d7400);
|
||||
// Normalized power of 2 - lower boundary is closer:
|
||||
b = value.assign_with_boundaries(1.9807040628566084e+28); // 2**94
|
||||
EXPECT_EQ(value, fp(0x0010000000000000, 42));
|
||||
EXPECT_EQ(b.lower, 0x7ffffffffffffe00);
|
||||
EXPECT_EQ(b.upper, 0x8000000000000400);
|
||||
// Smallest normalized double - equidistant boundaries:
|
||||
b = value.assign_with_boundaries(2.2250738585072014e-308);
|
||||
EXPECT_EQ(value, fp(0x0010000000000000, -1074));
|
||||
EXPECT_EQ(b.lower, 0x7ffffffffffffc00);
|
||||
EXPECT_EQ(b.upper, 0x8000000000000400);
|
||||
// Subnormal - equidistant boundaries:
|
||||
b = value.assign_with_boundaries(4.9406564584124654e-324);
|
||||
EXPECT_EQ(value, fp(0x0000000000000001, -1074));
|
||||
EXPECT_EQ(b.lower, 0x4000000000000000);
|
||||
EXPECT_EQ(b.upper, 0xc000000000000000);
|
||||
}
|
||||
|
||||
TEST(FPTest, DoubleTests) {
|
||||
TEST(fp_test, double_tests) {
|
||||
run_double_tests<std::numeric_limits<double>::is_iec559>();
|
||||
}
|
||||
|
||||
TEST(FPTest, Normalize) {
|
||||
TEST(fp_test, normalize) {
|
||||
const auto v = fp(0xbeef, 42);
|
||||
auto normalized = normalize(v);
|
||||
EXPECT_EQ(0xbeef000000000000, normalized.f);
|
||||
EXPECT_EQ(-6, normalized.e);
|
||||
}
|
||||
|
||||
TEST(FPTest, ComputeFloatBoundaries) {
|
||||
struct {
|
||||
double x, lower, upper;
|
||||
} tests[] = {
|
||||
// regular
|
||||
{1.5f, 1.4999999403953552, 1.5000000596046448},
|
||||
// boundary
|
||||
{1.0f, 0.9999999701976776, 1.0000000596046448},
|
||||
// min normal
|
||||
{1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38},
|
||||
// max subnormal
|
||||
{1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38},
|
||||
// min subnormal
|
||||
{1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45},
|
||||
};
|
||||
for (auto test : tests) {
|
||||
fp vlower = normalize(fp(test.lower));
|
||||
fp vupper = normalize(fp(test.upper));
|
||||
vlower.f >>= vupper.e - vlower.e;
|
||||
vlower.e = vupper.e;
|
||||
fp value;
|
||||
auto b = value.assign_float_with_boundaries(test.x);
|
||||
EXPECT_EQ(vlower.f, b.lower);
|
||||
EXPECT_EQ(vupper.f, b.upper);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FPTest, Multiply) {
|
||||
TEST(fp_test, multiply) {
|
||||
auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
|
||||
EXPECT_EQ(v.f, 123u * 56u);
|
||||
EXPECT_EQ(v.e, 4 + 7 + 64);
|
||||
@ -258,19 +203,57 @@ TEST(FPTest, Multiply) {
|
||||
EXPECT_EQ(v.e, 4 + 8 + 64);
|
||||
}
|
||||
|
||||
TEST(FPTest, GetCachedPower) {
|
||||
typedef std::numeric_limits<double> limits;
|
||||
TEST(fp_test, get_cached_power) {
|
||||
using limits = std::numeric_limits<double>;
|
||||
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
|
||||
int dec_exp = 0;
|
||||
auto fp = fmt::detail::get_cached_power(exp, dec_exp);
|
||||
EXPECT_LE(exp, fp.e);
|
||||
int dec_exp_step = 8;
|
||||
EXPECT_LE(fp.e, exp + dec_exp_step * log2(10));
|
||||
EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(static_cast<double>(fp.f), fp.e));
|
||||
bigint exact, cache(fp.f);
|
||||
if (dec_exp >= 0) {
|
||||
exact.assign_pow10(dec_exp);
|
||||
if (fp.e <= 0)
|
||||
exact <<= -fp.e;
|
||||
else
|
||||
cache <<= fp.e;
|
||||
exact.align(cache);
|
||||
cache.align(exact);
|
||||
auto exact_str = fmt::format("{}", exact);
|
||||
auto cache_str = fmt::format("{}", cache);
|
||||
EXPECT_EQ(exact_str.size(), cache_str.size());
|
||||
EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
|
||||
int diff = cache_str[15] - exact_str[15];
|
||||
if (diff == 1)
|
||||
EXPECT_GT(exact_str[16], '8');
|
||||
else
|
||||
EXPECT_EQ(diff, 0);
|
||||
} else {
|
||||
cache.assign_pow10(-dec_exp);
|
||||
cache *= fp.f + 1; // Inexact check.
|
||||
exact.assign(1);
|
||||
exact <<= -fp.e;
|
||||
exact.align(cache);
|
||||
auto exact_str = fmt::format("{}", exact);
|
||||
auto cache_str = fmt::format("{}", cache);
|
||||
EXPECT_EQ(exact_str.size(), cache_str.size());
|
||||
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FPTest, GetRoundDirection) {
|
||||
TEST(fp_test, dragonbox_max_k) {
|
||||
using fmt::detail::dragonbox::floor_log10_pow2;
|
||||
using float_info = fmt::detail::dragonbox::float_info<float>;
|
||||
EXPECT_EQ(fmt::detail::const_check(float_info::max_k),
|
||||
float_info::kappa - floor_log10_pow2(float_info::min_exponent -
|
||||
float_info::significand_bits));
|
||||
using double_info = fmt::detail::dragonbox::float_info<double>;
|
||||
EXPECT_EQ(
|
||||
fmt::detail::const_check(double_info::max_k),
|
||||
double_info::kappa - floor_log10_pow2(double_info::min_exponent -
|
||||
double_info::significand_bits));
|
||||
}
|
||||
|
||||
TEST(fp_test, get_round_direction) {
|
||||
using fmt::detail::get_round_direction;
|
||||
using fmt::detail::round_direction;
|
||||
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
|
||||
@ -294,116 +277,42 @@ TEST(FPTest, GetRoundDirection) {
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
|
||||
}
|
||||
|
||||
TEST(FPTest, FixedHandler) {
|
||||
struct handler : fmt::detail::fixed_handler {
|
||||
TEST(fp_test, fixed_handler) {
|
||||
struct handler : fmt::detail::gen_digits_handler {
|
||||
char buffer[10];
|
||||
handler(int prec = 0) : fmt::detail::fixed_handler() {
|
||||
handler(int prec = 0) : fmt::detail::gen_digits_handler() {
|
||||
buf = buffer;
|
||||
precision = prec;
|
||||
}
|
||||
};
|
||||
int exp = 0;
|
||||
handler().on_digit('0', 100, 99, 0, exp, false);
|
||||
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
|
||||
assertion_failure);
|
||||
handler().on_digit('0', 100, 99, 0, false);
|
||||
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure);
|
||||
namespace digits = fmt::detail::digits;
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done);
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error);
|
||||
// Check that divisor - error doesn't overflow.
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error);
|
||||
// Check that 2 * error doesn't overflow.
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false),
|
||||
digits::error);
|
||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error);
|
||||
}
|
||||
|
||||
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
|
||||
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
|
||||
fmt::memory_buffer buf;
|
||||
format_float(0.42, -1, fmt::detail::float_specs(), buf);
|
||||
}
|
||||
|
||||
template <typename T> struct value_extractor {
|
||||
T operator()(T value) { return value; }
|
||||
|
||||
template <typename U> FMT_NORETURN T operator()(U) {
|
||||
throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
|
||||
}
|
||||
|
||||
#if FMT_USE_INT128
|
||||
// Apple Clang does not define typeid for __int128_t and __uint128_t.
|
||||
FMT_NORETURN T operator()(fmt::detail::int128_t) {
|
||||
throw std::runtime_error("invalid type __int128_t");
|
||||
}
|
||||
|
||||
FMT_NORETURN T operator()(fmt::detail::uint128_t) {
|
||||
throw std::runtime_error("invalid type __uint128_t");
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
TEST(FormatTest, ArgConverter) {
|
||||
long long value = max_value<long long>();
|
||||
auto arg = fmt::detail::make_arg<fmt::format_context>(value);
|
||||
fmt::visit_format_arg(
|
||||
fmt::detail::arg_converter<long long, fmt::format_context>(arg, 'd'),
|
||||
arg);
|
||||
EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg));
|
||||
}
|
||||
|
||||
TEST(FormatTest, FormatNegativeNaN) {
|
||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
if (std::signbit(-nan))
|
||||
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
||||
else
|
||||
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
||||
}
|
||||
|
||||
TEST(FormatTest, StrError) {
|
||||
char* message = nullptr;
|
||||
char buffer[BUFFER_SIZE];
|
||||
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0),
|
||||
"invalid buffer");
|
||||
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0),
|
||||
"invalid buffer");
|
||||
buffer[0] = 'x';
|
||||
#if defined(_GNU_SOURCE) && !defined(__COVERITY__)
|
||||
// Use invalid error code to make sure that safe_strerror returns an error
|
||||
// message in the buffer rather than a pointer to a static string.
|
||||
int error_code = -1;
|
||||
#else
|
||||
int error_code = EDOM;
|
||||
#endif
|
||||
|
||||
int result =
|
||||
fmt::detail::safe_strerror(error_code, message = buffer, BUFFER_SIZE);
|
||||
EXPECT_EQ(result, 0);
|
||||
size_t message_size = std::strlen(message);
|
||||
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
|
||||
EXPECT_EQ(get_system_error(error_code), message);
|
||||
|
||||
// safe_strerror never uses buffer on MinGW.
|
||||
#if !defined(__MINGW32__) && !defined(__sun)
|
||||
result =
|
||||
fmt::detail::safe_strerror(error_code, message = buffer, message_size);
|
||||
EXPECT_EQ(ERANGE, result);
|
||||
result = fmt::detail::safe_strerror(error_code, message = buffer, 1);
|
||||
EXPECT_EQ(buffer, message); // Message should point to buffer.
|
||||
EXPECT_EQ(ERANGE, result);
|
||||
EXPECT_STREQ("", message);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(FormatTest, FormatErrorCode) {
|
||||
TEST(format_impl_test, format_error_code) {
|
||||
std::string msg = "error 42", sep = ": ";
|
||||
{
|
||||
fmt::memory_buffer buffer;
|
||||
format_to(buffer, "garbage");
|
||||
format_to(fmt::appender(buffer), "garbage");
|
||||
fmt::detail::format_error_code(buffer, 42, "test");
|
||||
EXPECT_EQ("test: " + msg, to_string(buffer));
|
||||
}
|
||||
{
|
||||
fmt::memory_buffer buffer;
|
||||
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size() + 1,
|
||||
'x');
|
||||
auto prefix =
|
||||
std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x');
|
||||
fmt::detail::format_error_code(buffer, 42, prefix);
|
||||
EXPECT_EQ(msg, to_string(buffer));
|
||||
}
|
||||
@ -412,7 +321,8 @@ TEST(FormatTest, FormatErrorCode) {
|
||||
// Test maximum buffer size.
|
||||
msg = fmt::format("error {}", codes[i]);
|
||||
fmt::memory_buffer buffer;
|
||||
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
|
||||
auto prefix =
|
||||
std::string(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
|
||||
fmt::detail::format_error_code(buffer, codes[i], prefix);
|
||||
EXPECT_EQ(prefix + sep + msg, to_string(buffer));
|
||||
size_t size = fmt::inline_buffer_size;
|
||||
@ -425,9 +335,9 @@ TEST(FormatTest, FormatErrorCode) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FormatTest, CountCodePoints) {
|
||||
TEST(format_impl_test, compute_width) {
|
||||
EXPECT_EQ(4,
|
||||
fmt::detail::count_code_points(
|
||||
fmt::detail::compute_width(
|
||||
fmt::basic_string_view<fmt::detail::char8_type>(
|
||||
reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
|
||||
}
|
||||
@ -442,15 +352,26 @@ template <typename Int> void test_count_digits() {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UtilTest, CountDigits) {
|
||||
TEST(format_impl_test, count_digits) {
|
||||
test_count_digits<uint32_t>();
|
||||
test_count_digits<uint64_t>();
|
||||
}
|
||||
|
||||
TEST(UtilTest, WriteFallbackUIntPtr) {
|
||||
TEST(format_impl_test, write_fallback_uintptr) {
|
||||
std::string s;
|
||||
fmt::detail::write_ptr<char>(
|
||||
std::back_inserter(s),
|
||||
fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr);
|
||||
EXPECT_EQ(s, "0xface");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(format_impl_test, write_console_signature) {
|
||||
decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW;
|
||||
(void)p;
|
||||
}
|
||||
#endif
|
||||
|
2788
test/format-test.cc
2788
test/format-test.cc
File diff suppressed because it is too large
Load Diff
@ -1,38 +1,30 @@
|
||||
# Copyright (c) 2019, Paul Dreik
|
||||
# License: see LICENSE.rst in the fmt root directory
|
||||
|
||||
# settings this links in a main. useful for reproducing,
|
||||
# kcov, gdb, afl, valgrind.
|
||||
# (note that libFuzzer can also reproduce, just pass it the files)
|
||||
option(FMT_FUZZ_LINKMAIN "enables the reproduce mode, instead of libFuzzer" On)
|
||||
# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind.
|
||||
# (Note that libFuzzer can also reproduce, just pass it the files.)
|
||||
option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On)
|
||||
|
||||
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
|
||||
# the fuzz targets, otherwise the cmake configuration step fails.
|
||||
# the fuzz targets, otherwise the CMake configuration step fails.
|
||||
set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
||||
|
||||
# Find all fuzzers.
|
||||
set(SOURCES
|
||||
chrono_duration.cpp
|
||||
named_arg.cpp
|
||||
one_arg.cpp
|
||||
sprintf.cpp
|
||||
two_args.cpp
|
||||
)
|
||||
|
||||
macro(implement_fuzzer sourcefile)
|
||||
get_filename_component(basename ${sourcefile} NAME_WE)
|
||||
set(name fuzzer_${basename})
|
||||
add_executable(${name} ${sourcefile} fuzzer_common.h)
|
||||
# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data
|
||||
# through the fuzzers.
|
||||
function(add_fuzzer source)
|
||||
get_filename_component(basename ${source} NAME_WE)
|
||||
set(name ${basename}-fuzzer)
|
||||
add_executable(${name} ${source} fuzzer-common.h)
|
||||
if (FMT_FUZZ_LINKMAIN)
|
||||
target_sources(${name} PRIVATE main.cpp)
|
||||
target_sources(${name} PRIVATE main.cc)
|
||||
endif ()
|
||||
target_link_libraries(${name} PRIVATE fmt)
|
||||
if (FMT_FUZZ_LDFLAGS)
|
||||
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
|
||||
endif ()
|
||||
if (FMT_FUZZ_LDFLAGS)
|
||||
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
|
||||
endif ()
|
||||
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
|
||||
endmacro ()
|
||||
endfunction()
|
||||
|
||||
foreach (X IN ITEMS ${SOURCES})
|
||||
implement_fuzzer(${X})
|
||||
foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)
|
||||
add_fuzzer(${source})
|
||||
endforeach ()
|
||||
|
@ -1,27 +1,4 @@
|
||||
# FMT Fuzzer
|
||||
|
||||
Fuzzing has revealed [several bugs](https://github.com/fmtlib/fmt/issues?&q=is%3Aissue+fuzz)
|
||||
in fmt. It is a part of the continous fuzzing at
|
||||
[oss-fuzz](https://github.com/google/oss-fuzz).
|
||||
|
||||
The source code is modified to make the fuzzing possible without locking up on
|
||||
resource exhaustion:
|
||||
```cpp
|
||||
#ifdef FMT_FUZZ
|
||||
if(spec.precision>100000) {
|
||||
throw std::runtime_error("fuzz mode - avoiding large precision");
|
||||
}
|
||||
#endif
|
||||
```
|
||||
This macro `FMT_FUZZ` is enabled on OSS-Fuzz builds and makes fuzzing
|
||||
practically possible. It is used in fmt code to prevent resource exhaustion in
|
||||
fuzzing mode.
|
||||
The macro `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` is the
|
||||
defacto standard for making fuzzing practically possible to disable certain
|
||||
fuzzing-unfriendly features (for example, randomness), see [the libFuzzer
|
||||
documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode).
|
||||
|
||||
## Running the fuzzers locally
|
||||
# Running the fuzzers locally
|
||||
|
||||
There is a [helper script](build.sh) to build the fuzzers, which has only been
|
||||
tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on
|
||||
@ -34,7 +11,7 @@ mkdir build
|
||||
cd build
|
||||
export CXX=clang++
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
|
||||
cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
cmake --build .
|
||||
```
|
||||
should work to build the fuzzers for all platforms which clang supports.
|
||||
@ -44,5 +21,5 @@ Execute a fuzzer with for instance
|
||||
cd build
|
||||
export UBSAN_OPTIONS=halt_on_error=1
|
||||
mkdir out_chrono
|
||||
bin/fuzzer_chrono_duration out_chrono
|
||||
bin/fuzzer_chrono_duration out_chrono
|
||||
```
|
||||
|
@ -1,7 +1,6 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Creates fuzzer builds of various kinds
|
||||
# - reproduce mode (no fuzzing, just enables replaying data through the fuzzers)
|
||||
# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
|
||||
# - libFuzzer build (you will need clang)
|
||||
# - afl build (you will need afl)
|
||||
@ -9,7 +8,7 @@
|
||||
#
|
||||
# Copyright (c) 2019 Paul Dreik
|
||||
#
|
||||
# License: see LICENSE.rst in the fmt root directory
|
||||
# For the license information refer to format.h.
|
||||
|
||||
set -e
|
||||
me=$(basename $0)
|
||||
@ -23,16 +22,9 @@ here=$(pwd)
|
||||
CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
|
||||
CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
|
||||
|
||||
#builds the fuzzers as one would do if using afl or just making
|
||||
#binaries for reproducing.
|
||||
builddir=$here/build-fuzzers-reproduce
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL" cmake \
|
||||
$CMAKEFLAGSALL
|
||||
cmake --build $builddir
|
||||
CLANG=clang++-11
|
||||
|
||||
#for performance analysis of the fuzzers
|
||||
# For performance analysis of the fuzzers.
|
||||
builddir=$here/build-fuzzers-perfanalysis
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
@ -43,11 +35,11 @@ $CMAKEFLAGSALL \
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
#builds the fuzzers as oss-fuzz does
|
||||
# Builds the fuzzers as oss-fuzz does.
|
||||
builddir=$here/build-fuzzers-ossfuzz
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXX=$CLANG \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
@ -56,11 +48,11 @@ cmake $CMAKEFLAGSALL \
|
||||
cmake --build $builddir
|
||||
|
||||
|
||||
#builds fuzzers for local fuzzing with libfuzzer with asan+usan
|
||||
# Builds fuzzers for local fuzzing with libfuzzer with asan+usan.
|
||||
builddir=$here/build-fuzzers-libfuzzer
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXX=$CLANG \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
@ -68,23 +60,11 @@ cmake $CMAKEFLAGSALL \
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
#builds fuzzers for local fuzzing with libfuzzer with asan only
|
||||
builddir=$here/build-fuzzers-libfuzzer-addr
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,undefined" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
#builds a fast fuzzer for making coverage fast
|
||||
# Builds a fast fuzzer for making coverage fast.
|
||||
builddir=$here/build-fuzzers-fast
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXX=$CLANG \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
@ -94,7 +74,7 @@ cmake $CMAKEFLAGSALL \
|
||||
cmake --build $builddir
|
||||
|
||||
|
||||
#builds fuzzers for local fuzzing with afl
|
||||
# Builds fuzzers for local fuzzing with afl.
|
||||
builddir=$here/build-fuzzers-afl
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
|
136
test/fuzzing/chrono-duration.cc
Normal file
136
test/fuzzing/chrono-duration.cc
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
template <typename Period, typename Rep>
|
||||
void invoke_inner(fmt::string_view format_str, Rep rep) {
|
||||
auto value = std::chrono::duration<Rep, Period>(rep);
|
||||
try {
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(format_str, value);
|
||||
#else
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buf), format_str, value);
|
||||
#endif
|
||||
} catch (std::exception&) {
|
||||
}
|
||||
}
|
||||
|
||||
// Rep is a duration's representation type.
|
||||
template <typename Rep>
|
||||
void invoke_outer(const uint8_t* data, size_t size, int period) {
|
||||
// Always use a fixed location of the data.
|
||||
static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small");
|
||||
if (size <= fixed_size + 1) return;
|
||||
|
||||
const Rep rep = assign_from_buf<Rep>(data);
|
||||
data += fixed_size;
|
||||
size -= fixed_size;
|
||||
|
||||
// data is already allocated separately in libFuzzer so reading past the end
|
||||
// will most likely be detected anyway.
|
||||
const auto format_str = fmt::string_view(as_chars(data), size);
|
||||
|
||||
// yocto, zepto, zetta and yotta are not handled.
|
||||
switch (period) {
|
||||
case 1:
|
||||
invoke_inner<std::atto>(format_str, rep);
|
||||
break;
|
||||
case 2:
|
||||
invoke_inner<std::femto>(format_str, rep);
|
||||
break;
|
||||
case 3:
|
||||
invoke_inner<std::pico>(format_str, rep);
|
||||
break;
|
||||
case 4:
|
||||
invoke_inner<std::nano>(format_str, rep);
|
||||
break;
|
||||
case 5:
|
||||
invoke_inner<std::micro>(format_str, rep);
|
||||
break;
|
||||
case 6:
|
||||
invoke_inner<std::milli>(format_str, rep);
|
||||
break;
|
||||
case 7:
|
||||
invoke_inner<std::centi>(format_str, rep);
|
||||
break;
|
||||
case 8:
|
||||
invoke_inner<std::deci>(format_str, rep);
|
||||
break;
|
||||
case 9:
|
||||
invoke_inner<std::deca>(format_str, rep);
|
||||
break;
|
||||
case 10:
|
||||
invoke_inner<std::kilo>(format_str, rep);
|
||||
break;
|
||||
case 11:
|
||||
invoke_inner<std::mega>(format_str, rep);
|
||||
break;
|
||||
case 12:
|
||||
invoke_inner<std::giga>(format_str, rep);
|
||||
break;
|
||||
case 13:
|
||||
invoke_inner<std::tera>(format_str, rep);
|
||||
break;
|
||||
case 14:
|
||||
invoke_inner<std::peta>(format_str, rep);
|
||||
break;
|
||||
case 15:
|
||||
invoke_inner<std::exa>(format_str, rep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (size <= 4) return 0;
|
||||
|
||||
const auto representation = data[0];
|
||||
const auto period = data[1];
|
||||
data += 2;
|
||||
size -= 2;
|
||||
|
||||
switch (representation) {
|
||||
case 1:
|
||||
invoke_outer<char>(data, size, period);
|
||||
break;
|
||||
case 2:
|
||||
invoke_outer<signed char>(data, size, period);
|
||||
break;
|
||||
case 3:
|
||||
invoke_outer<unsigned char>(data, size, period);
|
||||
break;
|
||||
case 4:
|
||||
invoke_outer<short>(data, size, period);
|
||||
break;
|
||||
case 5:
|
||||
invoke_outer<unsigned short>(data, size, period);
|
||||
break;
|
||||
case 6:
|
||||
invoke_outer<int>(data, size, period);
|
||||
break;
|
||||
case 7:
|
||||
invoke_outer<unsigned int>(data, size, period);
|
||||
break;
|
||||
case 8:
|
||||
invoke_outer<long>(data, size, period);
|
||||
break;
|
||||
case 9:
|
||||
invoke_outer<unsigned long>(data, size, period);
|
||||
break;
|
||||
case 10:
|
||||
invoke_outer<float>(data, size, period);
|
||||
break;
|
||||
case 11:
|
||||
invoke_outer<double>(data, size, period);
|
||||
break;
|
||||
case 12:
|
||||
invoke_outer<long double>(data, size, period);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
32
test/fuzzing/chrono-timepoint.cc
Normal file
32
test/fuzzing/chrono-timepoint.cc
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2021, Paul Dreik
|
||||
// For license information refer to format.h.
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
/*
|
||||
* a fuzzer for the chrono timepoints formatters
|
||||
* C is a clock (std::chrono::system_clock etc)
|
||||
*/
|
||||
template <typename C> void doit(const uint8_t* data, size_t size) {
|
||||
using Rep = typename C::time_point::rep;
|
||||
constexpr auto N = sizeof(Rep);
|
||||
if (size < N) return;
|
||||
|
||||
const auto x = assign_from_buf<Rep>(data);
|
||||
typename C::duration dur{x};
|
||||
typename C::time_point timepoint{dur};
|
||||
data += N;
|
||||
size -= N;
|
||||
data_to_string format_str(data, size);
|
||||
|
||||
std::string message = fmt::format(format_str.get(), timepoint);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
try {
|
||||
doit<std::chrono::system_clock>(data, size);
|
||||
} catch (...) {
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// License: see LICENSE.rst in the fmt root directory
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "fuzzer_common.h"
|
||||
|
||||
template <typename Item, typename Ratio>
|
||||
void invoke_inner(fmt::string_view formatstring, const Item item) {
|
||||
const std::chrono::duration<Item, Ratio> value(item);
|
||||
try {
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(formatstring, value);
|
||||
#else
|
||||
fmt::memory_buffer buf;
|
||||
fmt::format_to(buf, formatstring, value);
|
||||
#endif
|
||||
} catch (std::exception& /*e*/) {
|
||||
}
|
||||
}
|
||||
|
||||
// Item is the underlying type for duration (int, long etc)
|
||||
template <typename Item>
|
||||
void invoke_outer(const uint8_t* Data, size_t Size, const int scaling) {
|
||||
// always use a fixed location of the data
|
||||
using fmt_fuzzer::Nfixed;
|
||||
|
||||
constexpr auto N = sizeof(Item);
|
||||
static_assert(N <= Nfixed, "fixed size is too small");
|
||||
if (Size <= Nfixed + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
|
||||
|
||||
// fast forward
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
|
||||
// Data is already allocated separately in libFuzzer so reading past
|
||||
// the end will most likely be detected anyway
|
||||
const auto formatstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
||||
|
||||
// doit_impl<Item,std::yocto>(buf.data(),item);
|
||||
// doit_impl<Item,std::zepto>(buf.data(),item);
|
||||
switch (scaling) {
|
||||
case 1:
|
||||
invoke_inner<Item, std::atto>(formatstring, item);
|
||||
break;
|
||||
case 2:
|
||||
invoke_inner<Item, std::femto>(formatstring, item);
|
||||
break;
|
||||
case 3:
|
||||
invoke_inner<Item, std::pico>(formatstring, item);
|
||||
break;
|
||||
case 4:
|
||||
invoke_inner<Item, std::nano>(formatstring, item);
|
||||
break;
|
||||
case 5:
|
||||
invoke_inner<Item, std::micro>(formatstring, item);
|
||||
break;
|
||||
case 6:
|
||||
invoke_inner<Item, std::milli>(formatstring, item);
|
||||
break;
|
||||
case 7:
|
||||
invoke_inner<Item, std::centi>(formatstring, item);
|
||||
break;
|
||||
case 8:
|
||||
invoke_inner<Item, std::deci>(formatstring, item);
|
||||
break;
|
||||
case 9:
|
||||
invoke_inner<Item, std::deca>(formatstring, item);
|
||||
break;
|
||||
case 10:
|
||||
invoke_inner<Item, std::kilo>(formatstring, item);
|
||||
break;
|
||||
case 11:
|
||||
invoke_inner<Item, std::mega>(formatstring, item);
|
||||
break;
|
||||
case 12:
|
||||
invoke_inner<Item, std::giga>(formatstring, item);
|
||||
break;
|
||||
case 13:
|
||||
invoke_inner<Item, std::tera>(formatstring, item);
|
||||
break;
|
||||
case 14:
|
||||
invoke_inner<Item, std::peta>(formatstring, item);
|
||||
break;
|
||||
case 15:
|
||||
invoke_inner<Item, std::exa>(formatstring, item);
|
||||
}
|
||||
// doit_impl<Item,std::zeta>(buf.data(),item);
|
||||
// doit_impl<Item,std::yotta>(buf.data(),item);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
||||
if (Size <= 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto representation = Data[0];
|
||||
const auto scaling = Data[1];
|
||||
Data += 2;
|
||||
Size -= 2;
|
||||
|
||||
switch (representation) {
|
||||
case 1:
|
||||
invoke_outer<char>(Data, Size, scaling);
|
||||
break;
|
||||
case 2:
|
||||
invoke_outer<unsigned char>(Data, Size, scaling);
|
||||
break;
|
||||
case 3:
|
||||
invoke_outer<signed char>(Data, Size, scaling);
|
||||
break;
|
||||
case 4:
|
||||
invoke_outer<short>(Data, Size, scaling);
|
||||
break;
|
||||
case 5:
|
||||
invoke_outer<unsigned short>(Data, Size, scaling);
|
||||
break;
|
||||
case 6:
|
||||
invoke_outer<int>(Data, Size, scaling);
|
||||
break;
|
||||
case 7:
|
||||
invoke_outer<unsigned int>(Data, Size, scaling);
|
||||
break;
|
||||
case 8:
|
||||
invoke_outer<long>(Data, Size, scaling);
|
||||
break;
|
||||
case 9:
|
||||
invoke_outer<unsigned long>(Data, Size, scaling);
|
||||
break;
|
||||
case 10:
|
||||
invoke_outer<float>(Data, Size, scaling);
|
||||
break;
|
||||
case 11:
|
||||
invoke_outer<double>(Data, Size, scaling);
|
||||
break;
|
||||
case 12:
|
||||
invoke_outer<long double>(Data, Size, scaling);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
39
test/fuzzing/float.cc
Normal file
39
test/fuzzing/float.cc
Normal file
@ -0,0 +1,39 @@
|
||||
// A fuzzer for floating-point formatter.
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
void check_round_trip(fmt::string_view format_str, double value) {
|
||||
auto buffer = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buffer), format_str, value);
|
||||
|
||||
if (std::isnan(value)) {
|
||||
auto nan = std::signbit(value) ? "-nan" : "nan";
|
||||
if (fmt::string_view(buffer.data(), buffer.size()) != nan)
|
||||
throw std::runtime_error("round trip failure");
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.push_back('\0');
|
||||
char* ptr = nullptr;
|
||||
if (std::strtod(buffer.data(), &ptr) != value)
|
||||
throw std::runtime_error("round trip failure");
|
||||
if (ptr + 1 != buffer.end()) throw std::runtime_error("unparsed output");
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559)
|
||||
return 0;
|
||||
check_round_trip("{}", assign_from_buf<double>(data));
|
||||
// A larger than necessary precision is used to trigger the fallback
|
||||
// formatter.
|
||||
check_round_trip("{:.50g}", assign_from_buf<double>(data));
|
||||
return 0;
|
||||
}
|
77
test/fuzzing/fuzzer-common.h
Normal file
77
test/fuzzing/fuzzer-common.h
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FUZZER_COMMON_H
|
||||
#define FUZZER_COMMON_H
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <cstdint> // std::uint8_t
|
||||
#include <cstring> // memcpy
|
||||
#include <vector>
|
||||
|
||||
// One can format to either a string, or a buffer. The latter is faster, but
|
||||
// one may be interested in formatting to a string instead to verify it works
|
||||
// as intended. To avoid a combinatoric explosion, select this at compile time
|
||||
// instead of dynamically from the fuzz data.
|
||||
#define FMT_FUZZ_FORMAT_TO_STRING 0
|
||||
|
||||
// If {fmt} is given a buffer that is separately allocated, chances that address
|
||||
// sanitizer detects out of bound reads is much higher. However, it slows down
|
||||
// the fuzzing.
|
||||
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
||||
|
||||
// The size of the largest possible type in use.
|
||||
// To let the the fuzzer mutation be efficient at cross pollinating between
|
||||
// different types, use a fixed size format. The same bit pattern, interpreted
|
||||
// as another type, is likely interesting.
|
||||
constexpr auto fixed_size = 16;
|
||||
|
||||
// Casts data to a char pointer.
|
||||
template <typename T> inline const char* as_chars(const T* data) {
|
||||
return reinterpret_cast<const char*>(data);
|
||||
}
|
||||
|
||||
// Casts data to a byte pointer.
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return reinterpret_cast<const std::uint8_t*>(data);
|
||||
}
|
||||
|
||||
// Blits bytes from data to form an (assumed trivially constructible) object
|
||||
// of type Item.
|
||||
template <class Item> inline Item assign_from_buf(const std::uint8_t* data) {
|
||||
auto item = Item();
|
||||
std::memcpy(&item, data, sizeof(Item));
|
||||
return item;
|
||||
}
|
||||
|
||||
// Reads a boolean value by looking at the first byte from data.
|
||||
template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) {
|
||||
return *data != 0;
|
||||
}
|
||||
|
||||
struct data_to_string {
|
||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||
std::vector<char> buffer;
|
||||
|
||||
data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
|
||||
: buffer(size + (add_terminator ? 1 : 0)) {
|
||||
if (size) {
|
||||
std::memcpy(buffer.data(), data, size);
|
||||
}
|
||||
}
|
||||
|
||||
fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
|
||||
#else
|
||||
fmt::string_view sv;
|
||||
|
||||
data_to_string(const uint8_t* data, size_t size, bool = false)
|
||||
: str(as_chars(data), size) {}
|
||||
|
||||
fmt::string_view get() const { return sv; }
|
||||
#endif
|
||||
|
||||
const char* data() const { return get().data(); }
|
||||
};
|
||||
|
||||
#endif // FUZZER_COMMON_H
|
@ -1,67 +0,0 @@
|
||||
#ifndef FUZZER_COMMON_H
|
||||
#define FUZZER_COMMON_H
|
||||
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// License: see LICENSE.rst in the fmt root directory
|
||||
|
||||
#include <cstdint> // std::uint8_t
|
||||
#include <cstring> // memcpy
|
||||
#include <type_traits> // trivially copyable
|
||||
|
||||
// one can format to either a string, or a buf. buf is faster,
|
||||
// but one may be interested in formatting to a string instead to
|
||||
// verify it works as intended. to avoid a combinatoric explosion,
|
||||
// select this at compile time instead of dynamically from the fuzz data
|
||||
#define FMT_FUZZ_FORMAT_TO_STRING 0
|
||||
|
||||
// if fmt is given a buffer that is separately allocated,
|
||||
// chances that address sanitizer detects out of bound reads is
|
||||
// much higher. However, it slows down the fuzzing.
|
||||
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
||||
|
||||
// To let the the fuzzer mutation be efficient at cross pollinating
|
||||
// between different types, use a fixed size format.
|
||||
// The same bit pattern, interpreted as another type,
|
||||
// is likely interesting.
|
||||
// For this, we must know the size of the largest possible type in use.
|
||||
|
||||
// There are some problems on travis, claiming Nfixed is not a constant
|
||||
// expression which seems to be an issue with older versions of libstdc++
|
||||
#if _GLIBCXX_RELEASE >= 7
|
||||
# include <algorithm>
|
||||
namespace fmt_fuzzer {
|
||||
constexpr auto Nfixed = std::max(sizeof(long double), sizeof(std::intmax_t));
|
||||
}
|
||||
#else
|
||||
namespace fmt_fuzzer {
|
||||
constexpr auto Nfixed = 16;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace fmt_fuzzer {
|
||||
// view data as a c char pointer.
|
||||
template <typename T> inline const char* as_chars(const T* data) {
|
||||
return static_cast<const char*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
// view data as a byte pointer
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
// blits bytes from Data to form an (assumed trivially constructible) object
|
||||
// of type Item
|
||||
template <class Item> inline Item assignFromBuf(const std::uint8_t* Data) {
|
||||
Item item{};
|
||||
std::memcpy(&item, Data, sizeof(Item));
|
||||
return item;
|
||||
}
|
||||
|
||||
// reads a boolean value by looking at the first byte from Data
|
||||
template <> inline bool assignFromBuf<bool>(const std::uint8_t* Data) {
|
||||
return !!Data[0];
|
||||
}
|
||||
|
||||
} // namespace fmt_fuzzer
|
||||
|
||||
#endif // FUZZER_COMMON_H
|
22
test/fuzzing/main.cc
Normal file
22
test/fuzzing/main.cc
Normal file
@ -0,0 +1,22 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream in(argv[i]);
|
||||
assert(in);
|
||||
in.seekg(0, std::ios_base::end);
|
||||
const auto size = in.tellg();
|
||||
assert(size >= 0);
|
||||
in.seekg(0, std::ios_base::beg);
|
||||
std::vector<char> buf(static_cast<size_t>(size));
|
||||
in.read(buf.data(), size);
|
||||
assert(in.gcount() == size);
|
||||
LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "fuzzer_common.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size);
|
||||
int main(int argc, char* argv[]) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream in(argv[i]);
|
||||
assert(in);
|
||||
in.seekg(0, std::ios_base::end);
|
||||
const auto pos = in.tellg();
|
||||
assert(pos >= 0);
|
||||
in.seekg(0, std::ios_base::beg);
|
||||
std::vector<char> buf(static_cast<size_t>(pos));
|
||||
in.read(buf.data(), static_cast<long>(buf.size()));
|
||||
assert(in.gcount() == pos);
|
||||
LLVMFuzzerTestOneInput(fmt_fuzzer::as_bytes(buf.data()), buf.size());
|
||||
}
|
||||
}
|
102
test/fuzzing/named-arg.cc
Normal file
102
test/fuzzing/named-arg.cc
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
template <typename T>
|
||||
void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
|
||||
static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
|
||||
if (size <= fixed_size) return;
|
||||
const T value = assign_from_buf<T>(data);
|
||||
data += fixed_size;
|
||||
size -= fixed_size;
|
||||
|
||||
if (arg_name_size <= 0 || arg_name_size >= size) return;
|
||||
data_to_string arg_name(data, arg_name_size, true);
|
||||
data += arg_name_size;
|
||||
size -= arg_name_size;
|
||||
|
||||
data_to_string format_str(data, size);
|
||||
try {
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message =
|
||||
fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
|
||||
#else
|
||||
fmt::memory_buffer out;
|
||||
fmt::format_to(std::back_inserter(out), format_str.get(),
|
||||
fmt::arg(arg_name.data(), value));
|
||||
#endif
|
||||
} catch (std::exception&) {
|
||||
}
|
||||
}
|
||||
|
||||
// For dynamic dispatching to an explicit instantiation.
|
||||
template <typename Callback> void invoke(int type, Callback callback) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
callback(bool());
|
||||
break;
|
||||
case 1:
|
||||
callback(char());
|
||||
break;
|
||||
case 2:
|
||||
using sc = signed char;
|
||||
callback(sc());
|
||||
break;
|
||||
case 3:
|
||||
using uc = unsigned char;
|
||||
callback(uc());
|
||||
break;
|
||||
case 4:
|
||||
callback(short());
|
||||
break;
|
||||
case 5:
|
||||
using us = unsigned short;
|
||||
callback(us());
|
||||
break;
|
||||
case 6:
|
||||
callback(int());
|
||||
break;
|
||||
case 7:
|
||||
callback(unsigned());
|
||||
break;
|
||||
case 8:
|
||||
callback(long());
|
||||
break;
|
||||
case 9:
|
||||
using ul = unsigned long;
|
||||
callback(ul());
|
||||
break;
|
||||
case 10:
|
||||
callback(float());
|
||||
break;
|
||||
case 11:
|
||||
callback(double());
|
||||
break;
|
||||
case 12:
|
||||
using LD = long double;
|
||||
callback(LD());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (size <= 3) return 0;
|
||||
|
||||
// Switch types depending on the first byte of the input.
|
||||
const auto type = data[0] & 0x0F;
|
||||
const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
|
||||
data++;
|
||||
size--;
|
||||
|
||||
invoke(type, [=](auto arg) {
|
||||
invoke_fmt<decltype(arg)>(data, size, arg_name_size);
|
||||
});
|
||||
return 0;
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// License: see LICENSE.rst in the fmt root directory
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/core.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "fuzzer_common.h"
|
||||
|
||||
template <typename Item1>
|
||||
void invoke_fmt(const uint8_t* Data, size_t Size, unsigned int argsize) {
|
||||
constexpr auto N1 = sizeof(Item1);
|
||||
static_assert(N1 <= fmt_fuzzer::Nfixed, "Nfixed too small");
|
||||
if (Size <= fmt_fuzzer::Nfixed) {
|
||||
return;
|
||||
}
|
||||
const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
|
||||
|
||||
Data += fmt_fuzzer::Nfixed;
|
||||
Size -= fmt_fuzzer::Nfixed;
|
||||
|
||||
// how many chars should be used for the argument name?
|
||||
if (argsize <= 0 || argsize >= Size) {
|
||||
return;
|
||||
}
|
||||
|
||||
// allocating buffers separately is slower, but increases chances
|
||||
// of detecting memory errors
|
||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||
std::vector<char> argnamebuffer(argsize + 1);
|
||||
std::memcpy(argnamebuffer.data(), Data, argsize);
|
||||
auto argname = argnamebuffer.data();
|
||||
#else
|
||||
auto argname = fmt_fuzzer::as_chars(Data);
|
||||
#endif
|
||||
Data += argsize;
|
||||
Size -= argsize;
|
||||
|
||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||
// allocates as tight as possible, making it easier to catch buffer overruns.
|
||||
std::vector<char> fmtstringbuffer(Size);
|
||||
std::memcpy(fmtstringbuffer.data(), Data, Size);
|
||||
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
|
||||
#else
|
||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
||||
#endif
|
||||
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(fmtstring, fmt::arg(argname, item1));
|
||||
#else
|
||||
fmt::memory_buffer outbuf;
|
||||
fmt::format_to(outbuf, fmtstring, fmt::arg(argname, item1));
|
||||
#endif
|
||||
}
|
||||
|
||||
// for dynamic dispatching to an explicit instantiation
|
||||
template <typename Callback> void invoke(int index, Callback callback) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
callback(bool{});
|
||||
break;
|
||||
case 1:
|
||||
callback(char{});
|
||||
break;
|
||||
case 2:
|
||||
using sc = signed char;
|
||||
callback(sc{});
|
||||
break;
|
||||
case 3:
|
||||
using uc = unsigned char;
|
||||
callback(uc{});
|
||||
break;
|
||||
case 4:
|
||||
callback(short{});
|
||||
break;
|
||||
case 5:
|
||||
using us = unsigned short;
|
||||
callback(us{});
|
||||
break;
|
||||
case 6:
|
||||
callback(int{});
|
||||
break;
|
||||
case 7:
|
||||
callback(unsigned{});
|
||||
break;
|
||||
case 8:
|
||||
callback(long{});
|
||||
break;
|
||||
case 9:
|
||||
using ul = unsigned long;
|
||||
callback(ul{});
|
||||
break;
|
||||
case 10:
|
||||
callback(float{});
|
||||
break;
|
||||
case 11:
|
||||
callback(double{});
|
||||
break;
|
||||
case 12:
|
||||
using LD = long double;
|
||||
callback(LD{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
||||
if (Size <= 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// switch types depending on the first byte of the input
|
||||
const auto first = Data[0] & 0x0F;
|
||||
const unsigned int second = (Data[0] & 0xF0) >> 4;
|
||||
Data++;
|
||||
Size--;
|
||||
|
||||
auto outerfcn = [=](auto param1) {
|
||||
invoke_fmt<decltype(param1)>(Data, Size, second);
|
||||
};
|
||||
|
||||
try {
|
||||
invoke(first, outerfcn);
|
||||
} catch (std::exception& /*e*/) {
|
||||
}
|
||||
return 0;
|
||||
}
|
92
test/fuzzing/one-arg.cc
Normal file
92
test/fuzzing/one-arg.cc
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
template <typename T, typename Repr> const T* from_repr(const Repr& r) {
|
||||
return &r;
|
||||
}
|
||||
|
||||
template <> const std::tm* from_repr<std::tm>(const std::time_t& t) {
|
||||
return std::localtime(&t);
|
||||
}
|
||||
|
||||
template <typename T, typename Repr = T>
|
||||
void invoke_fmt(const uint8_t* data, size_t size) {
|
||||
static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small");
|
||||
if (size <= fixed_size) return;
|
||||
auto repr = assign_from_buf<Repr>(data);
|
||||
const T* value = from_repr<T>(repr);
|
||||
if (!value) return;
|
||||
data += fixed_size;
|
||||
size -= fixed_size;
|
||||
data_to_string format_str(data, size);
|
||||
try {
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(format_str.get(), *value);
|
||||
#else
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_to(message, format_str.get(), *value);
|
||||
#endif
|
||||
} catch (std::exception&) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (size <= 3) return 0;
|
||||
|
||||
const auto first = data[0];
|
||||
data++;
|
||||
size--;
|
||||
|
||||
switch (first) {
|
||||
case 0:
|
||||
invoke_fmt<bool>(data, size);
|
||||
break;
|
||||
case 1:
|
||||
invoke_fmt<char>(data, size);
|
||||
break;
|
||||
case 2:
|
||||
invoke_fmt<unsigned char>(data, size);
|
||||
break;
|
||||
case 3:
|
||||
invoke_fmt<signed char>(data, size);
|
||||
break;
|
||||
case 4:
|
||||
invoke_fmt<short>(data, size);
|
||||
break;
|
||||
case 5:
|
||||
invoke_fmt<unsigned short>(data, size);
|
||||
break;
|
||||
case 6:
|
||||
invoke_fmt<int>(data, size);
|
||||
break;
|
||||
case 7:
|
||||
invoke_fmt<unsigned int>(data, size);
|
||||
break;
|
||||
case 8:
|
||||
invoke_fmt<long>(data, size);
|
||||
break;
|
||||
case 9:
|
||||
invoke_fmt<unsigned long>(data, size);
|
||||
break;
|
||||
case 10:
|
||||
invoke_fmt<float>(data, size);
|
||||
break;
|
||||
case 11:
|
||||
invoke_fmt<double>(data, size);
|
||||
break;
|
||||
case 12:
|
||||
invoke_fmt<long double>(data, size);
|
||||
break;
|
||||
case 13:
|
||||
invoke_fmt<std::tm, std::time_t>(data, size);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// License: see LICENSE.rst in the fmt root directory
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include "fuzzer_common.h"
|
||||
|
||||
using fmt_fuzzer::Nfixed;
|
||||
|
||||
template <typename Item>
|
||||
void invoke_fmt(const uint8_t* Data, size_t Size) {
|
||||
constexpr auto N = sizeof(Item);
|
||||
static_assert(N <= Nfixed, "Nfixed is too small");
|
||||
if (Size <= Nfixed) {
|
||||
return;
|
||||
}
|
||||
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
|
||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||
// allocates as tight as possible, making it easier to catch buffer overruns.
|
||||
std::vector<char> fmtstringbuffer(Size);
|
||||
std::memcpy(fmtstringbuffer.data(), Data, Size);
|
||||
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
|
||||
#else
|
||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
||||
#endif
|
||||
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(fmtstring, item);
|
||||
#else
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_to(message, fmtstring, item);
|
||||
#endif
|
||||
}
|
||||
|
||||
void invoke_fmt_time(const uint8_t* Data, size_t Size) {
|
||||
using Item = std::time_t;
|
||||
constexpr auto N = sizeof(Item);
|
||||
static_assert(N <= Nfixed, "Nfixed too small");
|
||||
if (Size <= Nfixed) {
|
||||
return;
|
||||
}
|
||||
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||
// allocates as tight as possible, making it easier to catch buffer overruns.
|
||||
std::vector<char> fmtstringbuffer(Size);
|
||||
std::memcpy(fmtstringbuffer.data(), Data, Size);
|
||||
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
|
||||
#else
|
||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
||||
#endif
|
||||
auto* b = std::localtime(&item);
|
||||
if (b) {
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(fmtstring, *b);
|
||||
#else
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_to(message, fmtstring, *b);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
||||
if (Size <= 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto first = Data[0];
|
||||
Data++;
|
||||
Size--;
|
||||
|
||||
try {
|
||||
switch (first) {
|
||||
case 0:
|
||||
invoke_fmt<bool>(Data, Size);
|
||||
break;
|
||||
case 1:
|
||||
invoke_fmt<char>(Data, Size);
|
||||
break;
|
||||
case 2:
|
||||
invoke_fmt<unsigned char>(Data, Size);
|
||||
break;
|
||||
case 3:
|
||||
invoke_fmt<signed char>(Data, Size);
|
||||
break;
|
||||
case 4:
|
||||
invoke_fmt<short>(Data, Size);
|
||||
break;
|
||||
case 5:
|
||||
invoke_fmt<unsigned short>(Data, Size);
|
||||
break;
|
||||
case 6:
|
||||
invoke_fmt<int>(Data, Size);
|
||||
break;
|
||||
case 7:
|
||||
invoke_fmt<unsigned int>(Data, Size);
|
||||
break;
|
||||
case 8:
|
||||
invoke_fmt<long>(Data, Size);
|
||||
break;
|
||||
case 9:
|
||||
invoke_fmt<unsigned long>(Data, Size);
|
||||
break;
|
||||
case 10:
|
||||
invoke_fmt<float>(Data, Size);
|
||||
break;
|
||||
case 11:
|
||||
invoke_fmt<double>(Data, Size);
|
||||
break;
|
||||
case 12:
|
||||
invoke_fmt<long double>(Data, Size);
|
||||
break;
|
||||
case 13:
|
||||
invoke_fmt_time(Data, Size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (std::exception& /*e*/) {
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// License: see LICENSE.rst in the fmt root directory
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/printf.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "fuzzer_common.h"
|
||||
|
||||
using fmt_fuzzer::Nfixed;
|
||||
|
||||
template <typename Item1, typename Item2>
|
||||
void invoke_fmt(const uint8_t* Data, size_t Size) {
|
||||
constexpr auto N1 = sizeof(Item1);
|
||||
constexpr auto N2 = sizeof(Item2);
|
||||
static_assert(N1 <= Nfixed, "size1 exceeded");
|
||||
static_assert(N2 <= Nfixed, "size2 exceeded");
|
||||
if (Size <= Nfixed + Nfixed) {
|
||||
return;
|
||||
}
|
||||
Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
|
||||
Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data);
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
|
||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
||||
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(fmtstring, item1, item2);
|
||||
#else
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_to(message, fmtstring, item1, item2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// for dynamic dispatching to an explicit instantiation
|
||||
template <typename Callback> void invoke(int index, Callback callback) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
callback(bool{});
|
||||
break;
|
||||
case 1:
|
||||
callback(char{});
|
||||
break;
|
||||
case 2:
|
||||
using sc = signed char;
|
||||
callback(sc{});
|
||||
break;
|
||||
case 3:
|
||||
using uc = unsigned char;
|
||||
callback(uc{});
|
||||
break;
|
||||
case 4:
|
||||
callback(short{});
|
||||
break;
|
||||
case 5:
|
||||
using us = unsigned short;
|
||||
callback(us{});
|
||||
break;
|
||||
case 6:
|
||||
callback(int{});
|
||||
break;
|
||||
case 7:
|
||||
callback(unsigned{});
|
||||
break;
|
||||
case 8:
|
||||
callback(long{});
|
||||
break;
|
||||
case 9:
|
||||
using ul = unsigned long;
|
||||
callback(ul{});
|
||||
break;
|
||||
case 10:
|
||||
callback(float{});
|
||||
break;
|
||||
case 11:
|
||||
callback(double{});
|
||||
break;
|
||||
case 12:
|
||||
using LD = long double;
|
||||
callback(LD{});
|
||||
break;
|
||||
case 13:
|
||||
using ptr = void*;
|
||||
callback(ptr{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
||||
if (Size <= 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// switch types depending on the first byte of the input
|
||||
const auto first = Data[0] & 0x0F;
|
||||
const auto second = (Data[0] & 0xF0) >> 4;
|
||||
Data++;
|
||||
Size--;
|
||||
|
||||
auto outer = [=](auto param1) {
|
||||
auto inner = [=](auto param2) {
|
||||
invoke_fmt<decltype(param1), decltype(param2)>(Data, Size);
|
||||
};
|
||||
invoke(second, inner);
|
||||
};
|
||||
|
||||
try {
|
||||
invoke(first, outer);
|
||||
} catch (std::exception& /*e*/) {
|
||||
}
|
||||
return 0;
|
||||
}
|
106
test/fuzzing/two-args.cc
Normal file
106
test/fuzzing/two-args.cc
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include "fuzzer-common.h"
|
||||
|
||||
template <typename Item1, typename Item2>
|
||||
void invoke_fmt(const uint8_t* data, size_t size) {
|
||||
static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded");
|
||||
static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded");
|
||||
if (size <= fixed_size + fixed_size) return;
|
||||
|
||||
const Item1 item1 = assign_from_buf<Item1>(data);
|
||||
data += fixed_size;
|
||||
size -= fixed_size;
|
||||
|
||||
const Item2 item2 = assign_from_buf<Item2>(data);
|
||||
data += fixed_size;
|
||||
size -= fixed_size;
|
||||
|
||||
auto format_str = fmt::string_view(as_chars(data), size);
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(format_str, item1, item2);
|
||||
#else
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_to(message, format_str, item1, item2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// For dynamic dispatching to an explicit instantiation.
|
||||
template <typename Callback> void invoke(int index, Callback callback) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
callback(bool());
|
||||
break;
|
||||
case 1:
|
||||
callback(char());
|
||||
break;
|
||||
case 2:
|
||||
using sc = signed char;
|
||||
callback(sc());
|
||||
break;
|
||||
case 3:
|
||||
using uc = unsigned char;
|
||||
callback(uc());
|
||||
break;
|
||||
case 4:
|
||||
callback(short());
|
||||
break;
|
||||
case 5:
|
||||
using us = unsigned short;
|
||||
callback(us());
|
||||
break;
|
||||
case 6:
|
||||
callback(int());
|
||||
break;
|
||||
case 7:
|
||||
callback(unsigned());
|
||||
break;
|
||||
case 8:
|
||||
callback(long());
|
||||
break;
|
||||
case 9:
|
||||
using ul = unsigned long;
|
||||
callback(ul());
|
||||
break;
|
||||
case 10:
|
||||
callback(float());
|
||||
break;
|
||||
case 11:
|
||||
callback(double());
|
||||
break;
|
||||
case 12:
|
||||
using LD = long double;
|
||||
callback(LD());
|
||||
break;
|
||||
case 13:
|
||||
using ptr = void*;
|
||||
callback(ptr());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
if (size <= 3) return 0;
|
||||
|
||||
// Switch types depending on the first byte of the input.
|
||||
const auto type1 = data[0] & 0x0F;
|
||||
const auto type2 = (data[0] & 0xF0) >> 4;
|
||||
data++;
|
||||
size--;
|
||||
try {
|
||||
invoke(type1, [=](auto param1) {
|
||||
invoke(type2, [=](auto param2) {
|
||||
invoke_fmt<decltype(param1), decltype(param2)>(data, size);
|
||||
});
|
||||
});
|
||||
} catch (std::exception&) {
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
// Copyright (c) 2019, Paul Dreik
|
||||
// License: see LICENSE.rst in the fmt root directory
|
||||
#include <fmt/format.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include "fuzzer_common.h"
|
||||
|
||||
constexpr auto Nfixed = fmt_fuzzer::Nfixed;
|
||||
|
||||
template <typename Item1, typename Item2>
|
||||
void invoke_fmt(const uint8_t* Data, size_t Size) {
|
||||
constexpr auto N1 = sizeof(Item1);
|
||||
constexpr auto N2 = sizeof(Item2);
|
||||
static_assert(N1 <= Nfixed, "size1 exceeded");
|
||||
static_assert(N2 <= Nfixed, "size2 exceeded");
|
||||
if (Size <= Nfixed + Nfixed) {
|
||||
return;
|
||||
}
|
||||
const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
|
||||
const Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data);
|
||||
Data += Nfixed;
|
||||
Size -= Nfixed;
|
||||
|
||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
||||
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(fmtstring, item1, item2);
|
||||
#else
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_to(message, fmtstring, item1, item2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// for dynamic dispatching to an explicit instantiation
|
||||
template <typename Callback> void invoke(int index, Callback callback) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
callback(bool{});
|
||||
break;
|
||||
case 1:
|
||||
callback(char{});
|
||||
break;
|
||||
case 2:
|
||||
using sc = signed char;
|
||||
callback(sc{});
|
||||
break;
|
||||
case 3:
|
||||
using uc = unsigned char;
|
||||
callback(uc{});
|
||||
break;
|
||||
case 4:
|
||||
callback(short{});
|
||||
break;
|
||||
case 5:
|
||||
using us = unsigned short;
|
||||
callback(us{});
|
||||
break;
|
||||
case 6:
|
||||
callback(int{});
|
||||
break;
|
||||
case 7:
|
||||
callback(unsigned{});
|
||||
break;
|
||||
case 8:
|
||||
callback(long{});
|
||||
break;
|
||||
case 9:
|
||||
using ul = unsigned long;
|
||||
callback(ul{});
|
||||
break;
|
||||
case 10:
|
||||
callback(float{});
|
||||
break;
|
||||
case 11:
|
||||
callback(double{});
|
||||
break;
|
||||
case 12:
|
||||
using LD = long double;
|
||||
callback(LD{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
||||
if (Size <= 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// switch types depending on the first byte of the input
|
||||
const auto first = Data[0] & 0x0F;
|
||||
const auto second = (Data[0] & 0xF0) >> 4;
|
||||
Data++;
|
||||
Size--;
|
||||
|
||||
auto outer = [=](auto param1) {
|
||||
auto inner = [=](auto param2) {
|
||||
invoke_fmt<decltype(param1), decltype(param2)>(Data, Size);
|
||||
};
|
||||
invoke(second, inner);
|
||||
};
|
||||
|
||||
try {
|
||||
invoke(first, outer);
|
||||
} catch (std::exception& /*e*/) {
|
||||
}
|
||||
return 0;
|
||||
}
|
14204
test/gmock/gmock.h
14204
test/gmock/gmock.h
File diff suppressed because it is too large
Load Diff
@ -1,75 +0,0 @@
|
||||
// Formatting library for C++ - Grisu tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "gtest.h"
|
||||
|
||||
static bool reported_skipped;
|
||||
|
||||
#undef TEST
|
||||
#define TEST(test_fixture, test_name) \
|
||||
void test_fixture##test_name(); \
|
||||
GTEST_TEST(test_fixture, test_name) { \
|
||||
if (FMT_USE_GRISU) { \
|
||||
test_fixture##test_name(); \
|
||||
} else if (!reported_skipped) { \
|
||||
reported_skipped = true; \
|
||||
fmt::print("Skipping Grisu tests.\n"); \
|
||||
} \
|
||||
} \
|
||||
void test_fixture##test_name()
|
||||
|
||||
TEST(GrisuTest, NaN) {
|
||||
auto nan = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_EQ("nan", fmt::format("{}", nan));
|
||||
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
||||
}
|
||||
|
||||
TEST(GrisuTest, Inf) {
|
||||
auto inf = std::numeric_limits<double>::infinity();
|
||||
EXPECT_EQ("inf", fmt::format("{}", inf));
|
||||
EXPECT_EQ("-inf", fmt::format("{}", -inf));
|
||||
}
|
||||
|
||||
TEST(GrisuTest, Zero) { EXPECT_EQ("0.0", fmt::format("{}", 0.0)); }
|
||||
|
||||
TEST(GrisuTest, Round) {
|
||||
EXPECT_EQ("1.9156918820264798e-56",
|
||||
fmt::format("{}", 1.9156918820264798e-56));
|
||||
EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15));
|
||||
}
|
||||
|
||||
TEST(GrisuTest, Prettify) {
|
||||
EXPECT_EQ("0.0001", fmt::format("{}", 1e-4));
|
||||
EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
|
||||
EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
|
||||
EXPECT_EQ("10000000000.0", fmt::format("{}", 1e10));
|
||||
EXPECT_EQ("100000000000.0", fmt::format("{}", 1e11));
|
||||
EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7));
|
||||
EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
|
||||
EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
|
||||
EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
|
||||
EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
|
||||
}
|
||||
|
||||
TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
|
||||
|
||||
TEST(GrisuTest, Fallback) {
|
||||
EXPECT_EQ("1e+23", fmt::format("{}", 1e23));
|
||||
EXPECT_EQ("9e-265", fmt::format("{}", 9e-265));
|
||||
EXPECT_EQ("5.423717798060526e+125",
|
||||
fmt::format("{}", 5.423717798060526e+125));
|
||||
EXPECT_EQ("1.372371880954233e-288",
|
||||
fmt::format("{}", 1.372371880954233e-288));
|
||||
EXPECT_EQ("55388492.622190244", fmt::format("{}", 55388492.622190244));
|
||||
EXPECT_EQ("2.2506787569811123e-253",
|
||||
fmt::format("{}", 2.2506787569811123e-253));
|
||||
EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8));
|
||||
// pow(2, -25) - assymetric boundaries:
|
||||
EXPECT_EQ("2.9802322387695312e-08",
|
||||
fmt::format("{}", 2.9802322387695312e-08));
|
||||
}
|
@ -9,31 +9,18 @@
|
||||
|
||||
#include <gtest/gtest-spi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
# include <crtdbg.h> // for _CrtSetReportMode
|
||||
#endif // _WIN32
|
||||
|
||||
#include "fmt/os.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// This is used to suppress coverity warnings about untrusted values.
|
||||
std::string sanitize(const std::string& s) {
|
||||
std::string result;
|
||||
for (std::string::const_iterator i = s.begin(), end = s.end(); i != end; ++i)
|
||||
result.push_back(static_cast<char>(*i & 0xff));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tests that assertion macros evaluate their arguments exactly once.
|
||||
class SingleEvaluationTest : public ::testing::Test {
|
||||
namespace {
|
||||
class single_evaluation_test : public ::testing::Test {
|
||||
protected:
|
||||
SingleEvaluationTest() {
|
||||
single_evaluation_test() {
|
||||
p_ = s_;
|
||||
a_ = 0;
|
||||
b_ = 0;
|
||||
@ -45,11 +32,12 @@ class SingleEvaluationTest : public ::testing::Test {
|
||||
static int a_;
|
||||
static int b_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
const char* const SingleEvaluationTest::s_ = "01234";
|
||||
const char* SingleEvaluationTest::p_;
|
||||
int SingleEvaluationTest::a_;
|
||||
int SingleEvaluationTest::b_;
|
||||
const char* const single_evaluation_test::s_ = "01234";
|
||||
const char* single_evaluation_test::p_;
|
||||
int single_evaluation_test::a_;
|
||||
int single_evaluation_test::b_;
|
||||
|
||||
void do_nothing() {}
|
||||
|
||||
@ -61,7 +49,7 @@ FMT_NORETURN void throw_system_error() {
|
||||
|
||||
// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument
|
||||
// exactly once.
|
||||
TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) {
|
||||
TEST_F(single_evaluation_test, failed_expect_throw_msg) {
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234");
|
||||
EXPECT_EQ(s_ + 1, p_);
|
||||
@ -69,14 +57,14 @@ TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) {
|
||||
|
||||
// Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument
|
||||
// exactly once.
|
||||
TEST_F(SingleEvaluationTest, FailedEXPECT_SYSTEM_ERROR) {
|
||||
TEST_F(single_evaluation_test, failed_expect_system_error) {
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++),
|
||||
"01234");
|
||||
EXPECT_EQ(s_ + 1, p_);
|
||||
}
|
||||
|
||||
// Tests that assertion arguments are evaluated exactly once.
|
||||
TEST_F(SingleEvaluationTest, ExceptionTests) {
|
||||
TEST_F(single_evaluation_test, exception_tests) {
|
||||
// successful EXPECT_THROW_MSG
|
||||
EXPECT_THROW_MSG(
|
||||
{ // NOLINT
|
||||
@ -116,7 +104,7 @@ TEST_F(SingleEvaluationTest, ExceptionTests) {
|
||||
EXPECT_EQ(4, b_);
|
||||
}
|
||||
|
||||
TEST_F(SingleEvaluationTest, SystemErrorTests) {
|
||||
TEST_F(single_evaluation_test, system_error_tests) {
|
||||
// successful EXPECT_SYSTEM_ERROR
|
||||
EXPECT_SYSTEM_ERROR(
|
||||
{ // NOLINT
|
||||
@ -159,14 +147,14 @@ TEST_F(SingleEvaluationTest, SystemErrorTests) {
|
||||
#if FMT_USE_FCNTL
|
||||
// Tests that when EXPECT_WRITE fails, it evaluates its message argument
|
||||
// exactly once.
|
||||
TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
|
||||
TEST_F(single_evaluation_test, failed_expect_write) {
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
|
||||
"01234");
|
||||
EXPECT_EQ(s_ + 1, p_);
|
||||
}
|
||||
|
||||
// Tests that assertion arguments are evaluated exactly once.
|
||||
TEST_F(SingleEvaluationTest, WriteTests) {
|
||||
TEST_F(single_evaluation_test, write_tests) {
|
||||
// successful EXPECT_WRITE
|
||||
EXPECT_WRITE(
|
||||
stdout,
|
||||
@ -192,7 +180,7 @@ TEST_F(SingleEvaluationTest, WriteTests) {
|
||||
}
|
||||
|
||||
// Tests EXPECT_WRITE.
|
||||
TEST(ExpectTest, EXPECT_WRITE) {
|
||||
TEST(gtest_extra_test, expect_write) {
|
||||
EXPECT_WRITE(stdout, do_nothing(), "");
|
||||
EXPECT_WRITE(stdout, std::printf("test"), "test");
|
||||
EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
|
||||
@ -201,7 +189,7 @@ TEST(ExpectTest, EXPECT_WRITE) {
|
||||
" Actual: that");
|
||||
}
|
||||
|
||||
TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||
TEST(gtest_extra_test, expect_write_streaming) {
|
||||
EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
|
||||
<< "expected failure",
|
||||
@ -211,7 +199,7 @@ TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||
|
||||
// Tests that the compiler will not complain about unreachable code in the
|
||||
// EXPECT_THROW_MSG macro.
|
||||
TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
||||
int n = 0;
|
||||
using std::runtime_error;
|
||||
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
|
||||
@ -223,7 +211,7 @@ TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
|
||||
// Tests that the compiler will not complain about unreachable code in the
|
||||
// EXPECT_SYSTEM_ERROR macro.
|
||||
TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
|
||||
int n = 0;
|
||||
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
|
||||
@ -233,7 +221,7 @@ TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
"");
|
||||
}
|
||||
|
||||
TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
|
||||
TEST(gtest_extra_test, expect_throw_behaves_like_single_statement) {
|
||||
if (::testing::internal::AlwaysFalse())
|
||||
EXPECT_THROW_MSG(do_nothing(), std::exception, "");
|
||||
|
||||
@ -243,7 +231,7 @@ TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
|
||||
do_nothing();
|
||||
}
|
||||
|
||||
TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) {
|
||||
TEST(gtest_extra_test, expect_system_error_behaves_like_single_statement) {
|
||||
if (::testing::internal::AlwaysFalse())
|
||||
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "");
|
||||
|
||||
@ -253,7 +241,7 @@ TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) {
|
||||
do_nothing();
|
||||
}
|
||||
|
||||
TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) {
|
||||
TEST(gtest_extra_test, expect_write_behaves_like_single_statement) {
|
||||
if (::testing::internal::AlwaysFalse())
|
||||
EXPECT_WRITE(stdout, std::printf("x"), "x");
|
||||
|
||||
@ -264,7 +252,7 @@ TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) {
|
||||
}
|
||||
|
||||
// Tests EXPECT_THROW_MSG.
|
||||
TEST(ExpectTest, EXPECT_THROW_MSG) {
|
||||
TEST(gtest_extra_test, expect_throw_msg) {
|
||||
EXPECT_THROW_MSG(throw_exception(), std::exception, "test");
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"),
|
||||
@ -282,15 +270,15 @@ TEST(ExpectTest, EXPECT_THROW_MSG) {
|
||||
}
|
||||
|
||||
// Tests EXPECT_SYSTEM_ERROR.
|
||||
TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
|
||||
TEST(gtest_extra_test, expect_system_error) {
|
||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test");
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"),
|
||||
"Expected: throw_exception() throws an exception of "
|
||||
"type fmt::system_error.\n Actual: it throws a different type.");
|
||||
"type std::system_error.\n Actual: it throws a different type.");
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"),
|
||||
"Expected: do_nothing() throws an exception of type fmt::system_error.\n"
|
||||
"Expected: do_nothing() throws an exception of type std::system_error.\n"
|
||||
" Actual: it throws nothing.");
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"),
|
||||
@ -298,11 +286,11 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
|
||||
"throw_system_error() throws an exception with a different message.\n"
|
||||
"Expected: {}\n"
|
||||
" Actual: {}",
|
||||
format_system_error(EDOM, "other"),
|
||||
format_system_error(EDOM, "test")));
|
||||
system_error_message(EDOM, "other"),
|
||||
system_error_message(EDOM, "test")));
|
||||
}
|
||||
|
||||
TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
|
||||
TEST(gtest_extra_test, expect_throw_msg_streaming) {
|
||||
EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
|
||||
<< "unexpected failure";
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
@ -311,7 +299,7 @@ TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
|
||||
"expected failure");
|
||||
}
|
||||
|
||||
TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
|
||||
TEST(gtest_extra_test, expect_system_error_streaming) {
|
||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test")
|
||||
<< "unexpected failure";
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
@ -320,31 +308,19 @@ TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
|
||||
"expected failure");
|
||||
}
|
||||
|
||||
TEST(UtilTest, FormatSystemError) {
|
||||
fmt::memory_buffer out;
|
||||
fmt::format_system_error(out, EDOM, "test message");
|
||||
EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message"));
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
using fmt::file;
|
||||
|
||||
TEST(ErrorCodeTest, Ctor) {
|
||||
EXPECT_EQ(error_code().get(), 0);
|
||||
EXPECT_EQ(error_code(42).get(), 42);
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, ScopedRedirect) {
|
||||
TEST(output_redirect_test, scoped_redirect) {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
{
|
||||
buffered_file file(write_end.fdopen("w"));
|
||||
std::fprintf(file.get(), "[[[");
|
||||
{
|
||||
OutputRedirect redir(file.get());
|
||||
output_redirect redir(file.get());
|
||||
std::fprintf(file.get(), "censored");
|
||||
}
|
||||
std::fprintf(file.get(), "]]]");
|
||||
@ -352,8 +328,8 @@ TEST(OutputRedirectTest, ScopedRedirect) {
|
||||
EXPECT_READ(read_end, "[[[]]]");
|
||||
}
|
||||
|
||||
// Test that OutputRedirect handles errors in flush correctly.
|
||||
TEST(OutputRedirectTest, FlushErrorInCtor) {
|
||||
// Test that output_redirect handles errors in flush correctly.
|
||||
TEST(output_redirect_test, flush_error_in_ctor) {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int write_fd = write_end.descriptor();
|
||||
@ -362,47 +338,47 @@ TEST(OutputRedirectTest, FlushErrorInCtor) {
|
||||
// Put a character in a file buffer.
|
||||
EXPECT_EQ('x', fputc('x', f.get()));
|
||||
FMT_POSIX(close(write_fd));
|
||||
std::unique_ptr<OutputRedirect> redir{nullptr};
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new OutputRedirect(f.get())), EBADF,
|
||||
std::unique_ptr<output_redirect> redir{nullptr};
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new output_redirect(f.get())), EBADF,
|
||||
"cannot flush stream");
|
||||
redir.reset(nullptr);
|
||||
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, DupErrorInCtor) {
|
||||
TEST(output_redirect_test, dup_error_in_ctor) {
|
||||
buffered_file f = open_buffered_file();
|
||||
int fd = (f.fileno)();
|
||||
file copy = file::dup(fd);
|
||||
FMT_POSIX(close(fd));
|
||||
std::unique_ptr<OutputRedirect> redir{nullptr};
|
||||
std::unique_ptr<output_redirect> redir{nullptr};
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(
|
||||
redir.reset(new OutputRedirect(f.get())), EBADF,
|
||||
redir.reset(new output_redirect(f.get())), EBADF,
|
||||
fmt::format("cannot duplicate file descriptor {}", fd));
|
||||
copy.dup2(fd); // "undo" close or dtor will fail
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, RestoreAndRead) {
|
||||
TEST(output_redirect_test, restore_and_read) {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
buffered_file file(write_end.fdopen("w"));
|
||||
std::fprintf(file.get(), "[[[");
|
||||
OutputRedirect redir(file.get());
|
||||
output_redirect redir(file.get());
|
||||
std::fprintf(file.get(), "censored");
|
||||
EXPECT_EQ("censored", sanitize(redir.restore_and_read()));
|
||||
EXPECT_EQ("", sanitize(redir.restore_and_read()));
|
||||
EXPECT_EQ("censored", redir.restore_and_read());
|
||||
EXPECT_EQ("", redir.restore_and_read());
|
||||
std::fprintf(file.get(), "]]]");
|
||||
file = buffered_file();
|
||||
EXPECT_READ(read_end, "[[[]]]");
|
||||
}
|
||||
|
||||
// Test that OutputRedirect handles errors in flush correctly.
|
||||
TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
|
||||
TEST(output_redirect_test, flush_error_in_restore_and_read) {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int write_fd = write_end.descriptor();
|
||||
file write_copy = write_end.dup(write_fd);
|
||||
buffered_file f = write_end.fdopen("w");
|
||||
OutputRedirect redir(f.get());
|
||||
output_redirect redir(f.get());
|
||||
// Put a character in a file buffer.
|
||||
EXPECT_EQ('x', fputc('x', f.get()));
|
||||
FMT_POSIX(close(write_fd));
|
||||
@ -411,13 +387,13 @@ TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
|
||||
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, ErrorInDtor) {
|
||||
TEST(output_redirect_test, error_in_dtor) {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int write_fd = write_end.descriptor();
|
||||
file write_copy = write_end.dup(write_fd);
|
||||
buffered_file f = write_end.fdopen("w");
|
||||
std::unique_ptr<OutputRedirect> redir(new OutputRedirect(f.get()));
|
||||
std::unique_ptr<output_redirect> redir(new output_redirect(f.get()));
|
||||
// Put a character in a file buffer.
|
||||
EXPECT_EQ('x', fputc('x', f.get()));
|
||||
EXPECT_WRITE(
|
||||
@ -430,10 +406,8 @@ TEST(OutputRedirectTest, ErrorInDtor) {
|
||||
FMT_POSIX(close(write_fd));
|
||||
SUPPRESS_ASSERT(redir.reset(nullptr));
|
||||
},
|
||||
format_system_error(EBADF, "cannot flush stream"));
|
||||
system_error_message(EBADF, "cannot flush stream"));
|
||||
write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail
|
||||
}
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
|
||||
} // namespace
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
@ -11,24 +11,7 @@
|
||||
|
||||
using fmt::file;
|
||||
|
||||
void OutputRedirect::flush() {
|
||||
# if EOF != -1
|
||||
# error "FMT_RETRY assumes return value of -1 indicating failure"
|
||||
# endif
|
||||
int result = 0;
|
||||
FMT_RETRY(result, fflush(file_));
|
||||
if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
|
||||
}
|
||||
|
||||
void OutputRedirect::restore() {
|
||||
if (original_.descriptor() == -1) return; // Already restored.
|
||||
flush();
|
||||
// Restore the original file.
|
||||
original_.dup2(FMT_POSIX(fileno(file_)));
|
||||
original_.close();
|
||||
}
|
||||
|
||||
OutputRedirect::OutputRedirect(FILE* f) : file_(f) {
|
||||
output_redirect::output_redirect(FILE* f) : file_(f) {
|
||||
flush();
|
||||
int fd = FMT_POSIX(fileno(f));
|
||||
// Create a file object referring to the original file.
|
||||
@ -40,7 +23,7 @@ OutputRedirect::OutputRedirect(FILE* f) : file_(f) {
|
||||
write_end.dup2(fd);
|
||||
}
|
||||
|
||||
OutputRedirect::~OutputRedirect() FMT_NOEXCEPT {
|
||||
output_redirect::~output_redirect() FMT_NOEXCEPT {
|
||||
try {
|
||||
restore();
|
||||
} catch (const std::exception& e) {
|
||||
@ -48,7 +31,23 @@ OutputRedirect::~OutputRedirect() FMT_NOEXCEPT {
|
||||
}
|
||||
}
|
||||
|
||||
std::string OutputRedirect::restore_and_read() {
|
||||
void output_redirect::flush() {
|
||||
int result = 0;
|
||||
do {
|
||||
result = fflush(file_);
|
||||
} while (result == EOF && errno == EINTR);
|
||||
if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
|
||||
}
|
||||
|
||||
void output_redirect::restore() {
|
||||
if (original_.descriptor() == -1) return; // Already restored.
|
||||
flush();
|
||||
// Restore the original file.
|
||||
original_.dup2(FMT_POSIX(fileno(file_)));
|
||||
original_.close();
|
||||
}
|
||||
|
||||
std::string output_redirect::restore_and_read() {
|
||||
// Restore output.
|
||||
restore();
|
||||
|
||||
@ -79,9 +78,3 @@ std::string read(file& f, size_t count) {
|
||||
}
|
||||
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
std::string format_system_error(int error_code, fmt::string_view message) {
|
||||
fmt::memory_buffer out;
|
||||
format_system_error(out, error_code, message);
|
||||
return to_string(out);
|
||||
}
|
||||
|
@ -8,10 +8,17 @@
|
||||
#ifndef FMT_GTEST_EXTRA_H_
|
||||
#define FMT_GTEST_EXTRA_H_
|
||||
|
||||
#include <stdlib.h> // _invalid_parameter_handler
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "fmt/os.h"
|
||||
#include "gmock.h"
|
||||
#ifdef FMT_MODULE_TEST
|
||||
import fmt;
|
||||
#else
|
||||
# include "fmt/os.h"
|
||||
#endif // FMG_MODULE_TEST
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
@ -51,30 +58,35 @@
|
||||
FMT_TEST_THROW_(statement, expected_exception, expected_message, \
|
||||
GTEST_NONFATAL_FAILURE_)
|
||||
|
||||
std::string format_system_error(int error_code, fmt::string_view message);
|
||||
inline std::string system_error_message(int error_code,
|
||||
const std::string& message) {
|
||||
auto ec = std::error_code(error_code, std::generic_category());
|
||||
return std::system_error(ec, message).what();
|
||||
}
|
||||
|
||||
#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \
|
||||
EXPECT_THROW_MSG(statement, fmt::system_error, \
|
||||
format_system_error(error_code, message))
|
||||
EXPECT_THROW_MSG(statement, std::system_error, \
|
||||
system_error_message(error_code, message))
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
// Captures file output by redirecting it to a pipe.
|
||||
// The output it can handle is limited by the pipe capacity.
|
||||
class OutputRedirect {
|
||||
class output_redirect {
|
||||
private:
|
||||
FILE* file_;
|
||||
fmt::file original_; // Original file passed to redirector.
|
||||
fmt::file read_end_; // Read end of the pipe where the output is redirected.
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(OutputRedirect);
|
||||
|
||||
void flush();
|
||||
void restore();
|
||||
|
||||
public:
|
||||
explicit OutputRedirect(FILE* file);
|
||||
~OutputRedirect() FMT_NOEXCEPT;
|
||||
explicit output_redirect(FILE* file);
|
||||
~output_redirect() FMT_NOEXCEPT;
|
||||
|
||||
output_redirect(const output_redirect&) = delete;
|
||||
void operator=(const output_redirect&) = delete;
|
||||
|
||||
// Restores the original file, reads output from the pipe into a string
|
||||
// and returns it.
|
||||
@ -85,7 +97,7 @@ class OutputRedirect {
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
|
||||
std::string gtest_expected_output = expected_output; \
|
||||
OutputRedirect gtest_redir(file); \
|
||||
output_redirect gtest_redir(file); \
|
||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
||||
std::string gtest_output = gtest_redir.restore_and_read(); \
|
||||
if (gtest_output != gtest_expected_output) { \
|
||||
@ -106,7 +118,7 @@ class OutputRedirect {
|
||||
|
||||
// Suppresses Windows assertions on invalid file descriptors, making
|
||||
// POSIX functions return proper error codes instead of crashing on Windows.
|
||||
class SuppressAssert {
|
||||
class suppress_assert {
|
||||
private:
|
||||
_invalid_parameter_handler original_handler_;
|
||||
int original_report_mode_;
|
||||
@ -115,19 +127,20 @@ class SuppressAssert {
|
||||
const wchar_t*, unsigned, uintptr_t) {}
|
||||
|
||||
public:
|
||||
SuppressAssert()
|
||||
suppress_assert()
|
||||
: original_handler_(
|
||||
_set_invalid_parameter_handler(handle_invalid_parameter)),
|
||||
original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {}
|
||||
~SuppressAssert() {
|
||||
~suppress_assert() {
|
||||
_set_invalid_parameter_handler(original_handler_);
|
||||
_CrtSetReportMode(_CRT_ASSERT, original_report_mode_);
|
||||
(void)original_report_mode_;
|
||||
}
|
||||
};
|
||||
|
||||
# define SUPPRESS_ASSERT(statement) \
|
||||
{ \
|
||||
SuppressAssert sa; \
|
||||
suppress_assert sa; \
|
||||
statement; \
|
||||
}
|
||||
# else
|
||||
@ -141,16 +154,17 @@ class SuppressAssert {
|
||||
std::string read(fmt::file& f, size_t count);
|
||||
|
||||
# define EXPECT_READ(file, expected_content) \
|
||||
EXPECT_EQ(expected_content, \
|
||||
EXPECT_EQ(expected_content, \
|
||||
read(file, fmt::string_view(expected_content).size()))
|
||||
|
||||
#else
|
||||
# define EXPECT_WRITE(file, statement, expected_output) SUCCEED()
|
||||
# define EXPECT_WRITE(file, statement, expected_output) \
|
||||
do { \
|
||||
(void)(file); \
|
||||
(void)(statement); \
|
||||
(void)(expected_output); \
|
||||
SUCCEED(); \
|
||||
} while (false)
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {
|
||||
ScopedMock() { Mock::instance = this; }
|
||||
~ScopedMock() { Mock::instance = nullptr; }
|
||||
};
|
||||
|
||||
#endif // FMT_GTEST_EXTRA_H_
|
||||
|
3
test/gtest/.clang-format
Normal file
3
test/gtest/.clang-format
Normal file
@ -0,0 +1,3 @@
|
||||
# Disable clang-format here
|
||||
DisableFormat: true
|
||||
SortIncludes: Never
|
38
test/gtest/CMakeLists.txt
Normal file
38
test/gtest/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
#------------------------------------------------------------------------------
|
||||
# Build the google test library
|
||||
|
||||
# We compile Google Test ourselves instead of using pre-compiled libraries.
|
||||
# See the Google Test FAQ "Why is it not recommended to install a
|
||||
# pre-compiled copy of Google Test (for example, into /usr/local)?"
|
||||
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
|
||||
add_library(gtest STATIC
|
||||
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
|
||||
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
|
||||
target_include_directories(gtest SYSTEM PUBLIC .)
|
||||
|
||||
find_package(Threads)
|
||||
if (Threads_FOUND)
|
||||
target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT})
|
||||
else ()
|
||||
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
|
||||
endif ()
|
||||
|
||||
# Workaround GTest bug https://github.com/google/googletest/issues/705.
|
||||
check_cxx_compiler_flag(
|
||||
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
||||
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
||||
target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
||||
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# Disable MSVC warnings of POSIX functions.
|
||||
target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Silence MSVC tr1 deprecation warning in gmock.
|
||||
target_compile_definitions(gtest
|
||||
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
|
File diff suppressed because it is too large
Load Diff
11645
test/gtest/gmock/gmock.h
Normal file
11645
test/gtest/gmock/gmock.h
Normal file
File diff suppressed because it is too large
Load Diff
20065
test/gtest/gtest.h
20065
test/gtest/gtest.h
File diff suppressed because it is too large
Load Diff
@ -26,17 +26,21 @@
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Author: wan@google.com (Zhanyong Wan)
|
||||
|
||||
//
|
||||
// Utilities for testing Google Test itself and code that uses Google Test
|
||||
// (e.g. frameworks built on top of Google Test).
|
||||
|
||||
#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
// GOOGLETEST_CM0004 DO NOT DELETE
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// This helper class can be used to mock out Google Test failure reporting
|
||||
@ -68,14 +72,15 @@ class GTEST_API_ ScopedFakeTestPartResultReporter
|
||||
TestPartResultArray* result);
|
||||
|
||||
// The d'tor restores the previous test part result reporter.
|
||||
virtual ~ScopedFakeTestPartResultReporter();
|
||||
~ScopedFakeTestPartResultReporter() override;
|
||||
|
||||
// Appends the TestPartResult object to the TestPartResultArray
|
||||
// received in the constructor.
|
||||
//
|
||||
// This method is from the TestPartResultReporterInterface
|
||||
// interface.
|
||||
virtual void ReportTestPartResult(const TestPartResult& result);
|
||||
void ReportTestPartResult(const TestPartResult& result) override;
|
||||
|
||||
private:
|
||||
void Init();
|
||||
|
||||
@ -97,13 +102,12 @@ class GTEST_API_ SingleFailureChecker {
|
||||
public:
|
||||
// The constructor remembers the arguments.
|
||||
SingleFailureChecker(const TestPartResultArray* results,
|
||||
TestPartResult::Type type,
|
||||
const string& substr);
|
||||
TestPartResult::Type type, const std::string& substr);
|
||||
~SingleFailureChecker();
|
||||
private:
|
||||
const TestPartResultArray* const results_;
|
||||
const TestPartResult::Type type_;
|
||||
const string substr_;
|
||||
const std::string substr_;
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
|
||||
};
|
||||
@ -112,6 +116,8 @@ class GTEST_API_ SingleFailureChecker {
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
// A set of macros for testing Google Test assertions or code that's expected
|
||||
// to generate Google Test fatal failures. It verifies that the given
|
||||
// statement will cause exactly one fatal Google Test failure with 'substr'
|
||||
@ -229,4 +235,4 @@ class GTEST_API_ SingleFailureChecker {
|
||||
}\
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
12398
test/gtest/gtest/gtest.h
Normal file
12398
test/gtest/gtest/gtest.h
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user