mirror of
https://github.com/fmtlib/fmt.git
synced 2025-06-25 09:21:41 +02:00
Compare commits
2421 Commits
Author | SHA1 | Date | |
---|---|---|---|
5173a76ba4 | |||
1614af3520 | |||
569a9b3a7f | |||
4e7e3c65a3 | |||
0f7a6bfa12 | |||
4faec5a5ee | |||
7dbc8ac716 | |||
c87dd746fa | |||
372175caf8 | |||
9047548766 | |||
d30bca64e3 | |||
d6047cdc4a | |||
810241b36e | |||
661c474739 | |||
7c33059fad | |||
9e20883ab3 | |||
41899d5225 | |||
f42f459089 | |||
2381df654f | |||
7ae816563e | |||
c56cf3d074 | |||
01309a34ab | |||
a62d060554 | |||
23e3a2eee8 | |||
d8e0554b97 | |||
1e8eea4f44 | |||
44bd5384a9 | |||
20e19387a2 | |||
56fed78149 | |||
56e63078ff | |||
31ce6bc702 | |||
c9c5b90da7 | |||
1f3f84631c | |||
5de62af604 | |||
cbddab2fe2 | |||
f69b6eaabd | |||
ba363b3a24 | |||
a6f8e7d860 | |||
e753244ab6 | |||
98a7a8b405 | |||
3135d95fd9 | |||
8630a8f5ff | |||
cc3a88e6b3 | |||
79c4b6bd70 | |||
d130ee070f | |||
d0f90b5be7 | |||
6e080660dc | |||
31c3a24266 | |||
613b3b459d | |||
978521bb81 | |||
4e94c649f9 | |||
1a83443e6c | |||
8bef1c3b3a | |||
b287c37c65 | |||
2cac8a9d2e | |||
9a4cc88426 | |||
5ddf9ee1bd | |||
0b3a83f7f4 | |||
5aa5c98738 | |||
397ad1bec3 | |||
7431165f38 | |||
ee4d4c7fd0 | |||
ab2f8484e0 | |||
e900d735bb | |||
f4de7b684a | |||
1f8f5450b5 | |||
d702a68df2 | |||
e956a14e9e | |||
98dcc251eb | |||
5b8641dddf | |||
8c88abde64 | |||
23b976a615 | |||
9edee0e727 | |||
a909d42b76 | |||
16637341b9 | |||
2d71d7e030 | |||
d259fcfb05 | |||
704ed557a1 | |||
8603bd20d0 | |||
547f12ae63 | |||
f904e8a1b4 | |||
100e8af08a | |||
c11d0f056e | |||
2453ee576a | |||
47ae521557 | |||
936a1833c2 | |||
f2c9cb6244 | |||
d3107f8551 | |||
5e7c70e206 | |||
38cc68b3e5 | |||
6732ea5000 | |||
57ddc77ce7 | |||
50bad7d62d | |||
8f7a824e47 | |||
f11e968708 | |||
09737dd83b | |||
d9e3d6e6ed | |||
795b47a7b5 | |||
95c6ac0cc8 | |||
21409cfdda | |||
88c8d534e7 | |||
0f3eaeac0a | |||
344218510d | |||
16aec06179 | |||
1e1193590d | |||
0893c9c2ef | |||
3245145a41 | |||
57fc44907f | |||
7d22bebb6f | |||
8f2b5fe74d | |||
f095c67b6a | |||
5aabf1f715 | |||
19c5b5d159 | |||
519571edec | |||
ac8dfd841f | |||
2c6165a22b | |||
28639969ef | |||
f5fa1dee54 | |||
51bf9cfacb | |||
1a716caf5d | |||
98d4bbf813 | |||
8c8f74a870 | |||
bc1b89da26 | |||
a7fb321ac6 | |||
8cadb96506 | |||
297c3b2ed5 | |||
943532fece | |||
bd8804019b | |||
f230300ac5 | |||
a265e25b79 | |||
2aa2526f64 | |||
8d78045e7c | |||
7aafa6bc64 | |||
c66aae1652 | |||
6d66de3805 | |||
6b219a58db | |||
eee2023c2a | |||
c5ed73aab2 | |||
ea1cd9638c | |||
d3964d7b1e | |||
d18c6723a2 | |||
96c18b26c2 | |||
ba25baeb97 | |||
981b517ccf | |||
922ea924bf | |||
e0d98923c7 | |||
8069265373 | |||
963ee08310 | |||
02a6fe59fb | |||
de290f5c4c | |||
d0623de510 | |||
73e335ed38 | |||
b4d46e3988 | |||
a182f7341e | |||
68201831a5 | |||
7f723fbcb8 | |||
c06851456d | |||
2f05054dd3 | |||
f0ce21164c | |||
44639b11fe | |||
1c86a99e8f | |||
8f511fc12f | |||
59fe455f36 | |||
b0f47a13e6 | |||
d6cea50d01 | |||
40bc7163fb | |||
080e44d0bf | |||
7e57cace5d | |||
7b66e2f219 | |||
bab3f58003 | |||
9cc7edfddc | |||
8d9d528bf5 | |||
8efd1a8ef6 | |||
a71bc9c825 | |||
60d85d598c | |||
c3099beb6f | |||
cbb4cb8991 | |||
b85e9ac38b | |||
e3710ab972 | |||
d59751f0f0 | |||
d6abb2fa03 | |||
e9fdea90b5 | |||
44b6584f22 | |||
78f041ab5b | |||
7ca89bf87a | |||
3c114d091b | |||
e2ef12a8c0 | |||
bca82719ab | |||
99da389627 | |||
f19d667943 | |||
3e69847616 | |||
9ac1eebd47 | |||
e2ff910675 | |||
f2ed03b919 | |||
9dde9f0131 | |||
b1af642d1d | |||
4a617f25c6 | |||
6f435f55c8 | |||
cb475cb884 | |||
1e1ac6e964 | |||
e51c449fe6 | |||
0463665ef1 | |||
7d748a6f82 | |||
2b75bd7ce6 | |||
4a1d5931cb | |||
811b0f9054 | |||
450e8eed97 | |||
b8fbcec1be | |||
56bc86ffac | |||
3f79357ef0 | |||
8a11148f99 | |||
e371e8b686 | |||
813732fede | |||
3670d5b3f6 | |||
9e2ad7cf68 | |||
63479c8519 | |||
5944fcad37 | |||
e253b371b2 | |||
0c86f467bc | |||
1929df4bc2 | |||
a138221813 | |||
04e0dfd4bd | |||
04cde756bc | |||
c9a57b9a81 | |||
f46f5ecaf0 | |||
6e8d7e2776 | |||
567ed03f88 | |||
c3fa333140 | |||
84898b4626 | |||
538d83fd08 | |||
8a4630686e | |||
a9d62d3f35 | |||
fdcf7870a2 | |||
5899267c47 | |||
07b4c246ea | |||
e99809f29d | |||
3cd5179f32 | |||
7404e33a73 | |||
3aab2171ed | |||
7645ca0724 | |||
e30d8391e4 | |||
8cd8ef03eb | |||
bbb6b357c7 | |||
36ea32640f | |||
141a00d642 | |||
3860edc5d9 | |||
7d01859ef1 | |||
63b23e786a | |||
4999796c15 | |||
34b3f7b7aa | |||
27e3c0fe9b | |||
9bdd1596ce | |||
d151562bdd | |||
346500e70b | |||
a434a8f778 | |||
9eb47d951a | |||
51c58a56ba | |||
3fc33f6273 | |||
2e32db5b99 | |||
c1ce6e01f7 | |||
1c3c80dc1f | |||
d1d653d895 | |||
73c8437485 | |||
e588b02b17 | |||
1a62711d01 | |||
5b02881582 | |||
a133187a8c | |||
80ce222ca6 | |||
770a94edef | |||
2864e8432a | |||
21a295c272 | |||
96c68afe69 | |||
664dd88e31 | |||
69779b4ed6 | |||
01a172c969 | |||
08ca40ea91 | |||
dd97f4920c | |||
2951169481 | |||
d3e668418f | |||
52d0e1bbe3 | |||
5d32ccfc31 | |||
3cf619de55 | |||
2559983e7a | |||
026f99178e | |||
9f70fc3e7a | |||
6012dc9ab4 | |||
85050aa2e6 | |||
ff486a72a7 | |||
678341275b | |||
6f01b6ebb6 | |||
61c5a51604 | |||
02bfd8a9a5 | |||
3c24052cf1 | |||
f72a905eb3 | |||
941d5e147a | |||
ee2b828b9a | |||
5bb8856655 | |||
1c0c59d4a0 | |||
b1adaa9881 | |||
48e8d0ebef | |||
ce00979152 | |||
db4a6cfbf9 | |||
29a1ea795a | |||
8a06ca84c7 | |||
153f753bde | |||
eafd079868 | |||
0c6919ec72 | |||
197a5c3721 | |||
68742e1d87 | |||
1e8493196e | |||
58e6c84f5a | |||
75a4525e5f | |||
6ccb2e241b | |||
bed134a4aa | |||
b2d3a86ec0 | |||
13d82e32bd | |||
2161a73f2b | |||
e00997b004 | |||
0415cf2350 | |||
3bafd0749b | |||
f733882b55 | |||
dc22360c34 | |||
1f1b50707c | |||
24924128e3 | |||
c54cd71800 | |||
43e9b29e50 | |||
b55ea58705 | |||
4098970db2 | |||
314e15001f | |||
f499b393d1 | |||
6c30f41443 | |||
1acb73f970 | |||
09a13244c8 | |||
419db8baa1 | |||
9fc4161f5e | |||
25d6916b3a | |||
0b2eb6501c | |||
fd1cabe464 | |||
a844d7ab81 | |||
47d3968092 | |||
7800173eb1 | |||
b4218aa0f8 | |||
8a3a8177d6 | |||
e5f2f8ce7a | |||
75765bfad5 | |||
9bd9738da0 | |||
bd5f903f28 | |||
06e437fd98 | |||
1bd4f54fa6 | |||
11cc2903e4 | |||
b124e3e8e7 | |||
ffd5f3469f | |||
0f0e5ddf5f | |||
1f110702a1 | |||
4ccbe4b5f2 | |||
40638a75b3 | |||
c8dd9cc99d | |||
4bbe57cebf | |||
55b6130055 | |||
ae3ea156ea | |||
77165fdf85 | |||
65ac626c58 | |||
cd0b3f9695 | |||
cef1e4354b | |||
0201c8db21 | |||
9e3f3e8cff | |||
aa07c57654 | |||
a73d89e9c7 | |||
cb8e7caf7c | |||
b3fd0005dd | |||
7b478f9dec | |||
c85efef312 | |||
455a7c0787 | |||
674c326d7c | |||
061a9897fe | |||
d2d1c9c560 | |||
b6e19e5953 | |||
f219dcd59b | |||
dea7fde8b7 | |||
5390e29d42 | |||
9f6434dcde | |||
dac9a7f99d | |||
3ca9533f38 | |||
7eec036d9a | |||
e6b37b4aff | |||
8cf4c52068 | |||
74532c23a3 | |||
b308159be5 | |||
162995fedd | |||
8b41362a0a | |||
1b1c70108a | |||
d7e72a09e0 | |||
2201890d7a | |||
6100ed4bb3 | |||
1afe201ae8 | |||
cd2b99032f | |||
9acf89fef6 | |||
9ea42fb26e | |||
da2569827e | |||
35959a31d7 | |||
ec2463c905 | |||
0012917f69 | |||
9e450911fa | |||
068d20bc31 | |||
a99fbe67b9 | |||
adbed11ed4 | |||
8ab1c5c6e8 | |||
a770009fcc | |||
598e6042d1 | |||
e09814dc93 | |||
b272fb3605 | |||
f94b7364b9 | |||
7abec071b5 | |||
b7eb8c8921 | |||
ae7c50185d | |||
9f2e7edaeb | |||
fd52de0c6b | |||
f675cb887e | |||
73a16b827f | |||
72879db40e | |||
d3aa0c3a28 | |||
31de9a1b80 | |||
227bfe62dd | |||
95dfdc6cc4 | |||
5916ff63c4 | |||
1ab80aa92c | |||
4f4d876616 | |||
f443bd3baf | |||
1219b65f21 | |||
071794ec65 | |||
d22e4ad85b | |||
983806b0c1 | |||
02af5beb8a | |||
123e7f7fc3 | |||
168460f02c | |||
a64f60c849 | |||
1a599117d8 | |||
b160123e39 | |||
5981588565 | |||
8bbe76af3a | |||
4ca6821e8f | |||
7111a1eb9f | |||
ae00bbdc91 | |||
e71e07d9fb | |||
0184df7020 | |||
1cbae6e9ba | |||
159f89e2b8 | |||
4b120b68ae | |||
186b225d9d | |||
4cbf4888ea | |||
e31f2b3d03 | |||
62da1db62a | |||
3bc28fcc6b | |||
3c05fa46c6 | |||
ba6e330fd3 | |||
6037b3cae9 | |||
fafb03fa6d | |||
2f9acd1838 | |||
aaf829bfb1 | |||
b994a0ab13 | |||
bb205d940d | |||
ef7369ce90 | |||
40e4c227db | |||
ea54b21e78 | |||
9cbf4b087c | |||
1200a34e10 | |||
9c7e2a6c6f | |||
34e921f6fe | |||
c3be0f593d | |||
c68703c9f4 | |||
9a21728b0a | |||
3de36e9348 | |||
4afb39bc24 | |||
7ffa62db18 | |||
0d07db1234 | |||
d19ed6716d | |||
99b6e928d4 | |||
57cd3f72e9 | |||
111fc127fe | |||
6003ec3f25 | |||
8877a67724 | |||
75fff1db64 | |||
28d7191c27 | |||
43271ba8e8 | |||
63a9f3fcd4 | |||
4cf59ce734 | |||
7395472dde | |||
9108b25da9 | |||
4d366c68b7 | |||
ded1e7679e | |||
c7edd8e570 | |||
75108a56f6 | |||
3e1f70fe02 | |||
125fc5e520 | |||
6793ffc1d6 | |||
f4fcc5fd28 | |||
4de41aa655 | |||
404a880bd4 | |||
092d2dc7b2 | |||
093e554211 | |||
d0696b0aa6 | |||
66d7746bb3 | |||
e9bff78814 | |||
57b6f2966d | |||
d79493e5ee | |||
78842ce0d6 | |||
5420bcce2d | |||
56a2e2075c | |||
ed117baa4f | |||
f26446290b | |||
7e1cb3237a | |||
f67783d7e6 | |||
1c6d85f7bb | |||
4a1da44f91 | |||
080b6899d2 | |||
c01ec54fde | |||
b0c2ab93fa | |||
9b7fe2a4a1 | |||
c58b7d9c2f | |||
ceff9b0b2e | |||
3dc8639f8a | |||
dcde089b4e | |||
2145a7bdcc | |||
52ae134f84 | |||
0d6dd0cc6a | |||
1f918159ed | |||
6868f888b2 | |||
87cd545a1e | |||
12f9437e22 | |||
bb0c8bfea8 | |||
e6e8298904 | |||
0f0848e4f4 | |||
a1fb5c7337 | |||
8a411c2bca | |||
0047dc10a2 | |||
263cdef8a0 | |||
d4ca54253a | |||
5bb7b28e15 | |||
1409dfe76b | |||
f1559e1d56 | |||
ffd05e65ed | |||
0889856d61 | |||
d6eede9e08 | |||
213e09644f | |||
6bfc9af8c9 | |||
3487f1b9cd | |||
791294d17b | |||
8e700619b7 | |||
58c6f8c7f5 | |||
40414b3446 | |||
b7a157401e | |||
7aa58c30bf | |||
8e9bffa986 | |||
ce4d87acd4 | |||
21acc2af43 | |||
00669427df | |||
d39ebf3ff2 | |||
6498bc6d31 | |||
a967dcbe20 | |||
8498bc97dd | |||
e2ea940673 | |||
2bc5585ff0 | |||
bb728a572a | |||
36d1390e67 | |||
599e0aef45 | |||
91f7619cc9 | |||
c4dc6bef24 | |||
646966e973 | |||
a5abe5d95c | |||
1cbc5fa6cb | |||
f7a5748fd3 | |||
0e94b931a2 | |||
5e58eb97b1 | |||
3a15ea3ea5 | |||
b87ac4d840 | |||
a927dda9bb | |||
dd11d45847 | |||
b55551f900 | |||
96f91428c6 | |||
b732f28c00 | |||
a82c1dc6d9 | |||
2730e90186 | |||
e4d6d9d7c8 | |||
a1079e9fd6 | |||
b66bb6b71f | |||
b60114533f | |||
c41cea8b18 | |||
0c7650373c | |||
0571013709 | |||
d2c9276fcd | |||
0fc7bd1573 | |||
b4f1988c4b | |||
4b8f8fac96 | |||
3b2fc033d1 | |||
ac59d9f3a4 | |||
8f27ce4d8b | |||
89b0c71fa9 | |||
f6a783ad2e | |||
ccc8f5db02 | |||
20fdb88a1c | |||
f29901097f | |||
758446c80d | |||
f7aedc5fc4 | |||
840a817ed2 | |||
79c923ba2c | |||
5dc577c064 | |||
c6d1a94a9f | |||
0656045d02 | |||
c85ae23c73 | |||
b3bf665764 | |||
0887887e23 | |||
ac71d853be | |||
6649b8e0ca | |||
56b5c192a0 | |||
b2f0b6e44e | |||
3d9f3c163b | |||
19547d5148 | |||
972ffd3151 | |||
2ed412fa38 | |||
df4dcf2ece | |||
f7a4b4ab91 | |||
611cf0b3c6 | |||
58a8f2f539 | |||
1882b9687b | |||
6de0454b42 | |||
16e3c48bb0 | |||
8ce5f680f2 | |||
2fd8f9ec8a | |||
fe642d7648 | |||
a128b5b2cb | |||
466128de00 | |||
22e98a5b6a | |||
f18a3f36a7 | |||
7cad33563c | |||
e1ab6bc006 | |||
24a88545d9 | |||
422e7b9d70 | |||
d1dd9d5327 | |||
9a56a608ee | |||
c76957565c | |||
200ee6f108 | |||
bcd9b9331a | |||
345ba07f1d | |||
9e2490be4c | |||
3f75e2b69e | |||
744302add0 | |||
f5556225a4 | |||
ad3c7855e2 | |||
7512a55aa3 | |||
9bd2f1f9af | |||
b9815cf048 | |||
fe00cddde2 | |||
1a7d172dc7 | |||
006c2546f2 | |||
4ce006fb6e | |||
e2e557e273 | |||
a5f470eb10 | |||
4070c1d80b | |||
f8b59251c4 | |||
643aa4c8c4 | |||
ffe29a92f4 | |||
e94d723667 | |||
a9337395a2 | |||
562a9f4991 | |||
b257c56e51 | |||
1488df3395 | |||
b918e3ff81 | |||
23b04ca6d5 | |||
5d5918a74d | |||
413d97b33a | |||
790fd90f40 | |||
c0890be77d | |||
5a4b24613b | |||
45dc3486fe | |||
5a353fa8cf | |||
c84d227da9 | |||
b2d4ca1546 | |||
2aae6b120c | |||
9f09b8eed1 | |||
2bb8120d98 | |||
c1e97392be | |||
4e99e09bb3 | |||
1607a01870 | |||
e8219952c6 | |||
544b537334 | |||
83c13a1fe3 | |||
2546dafece | |||
02c04b1736 | |||
5360ab0b59 | |||
b615eca964 | |||
22a42c0b01 | |||
c63624ed45 | |||
211d312406 | |||
ef55e74e08 | |||
612669d343 | |||
ba63ac8c18 | |||
3fe49163bd | |||
a5bd3ddb28 | |||
3df0ea34e5 | |||
436acf3489 | |||
823128049b | |||
1dfa2591b3 | |||
6f2dd30e38 | |||
431d0f85cf | |||
ad71f5a706 | |||
d0f2f3b816 | |||
eac2796ce3 | |||
ee36257469 | |||
bbf0bada33 | |||
3730b4f039 | |||
25ff2efc0a | |||
9393fe26f6 | |||
b48ffc14a4 | |||
3268531bc9 | |||
8bd59ec936 | |||
1235f0a24a | |||
4fb73d1145 | |||
f9ceefb0fe | |||
6bcc3fd216 | |||
6a497e1d06 | |||
e9b9b0aefa | |||
ec24342b2b | |||
230b24944c | |||
cadd92d69f | |||
41076d1de3 | |||
d561cb47a7 | |||
699fe8e711 | |||
da2d33f1f1 | |||
af4734fd15 | |||
a3a74672a0 | |||
c11e68305f | |||
dd8cc8b0ba | |||
f6f0415b83 | |||
c92dc37464 | |||
e3f20d3e13 | |||
b438812324 | |||
bc15e3700e | |||
cf5ebf2059 | |||
9c20e72de3 | |||
79209598f5 | |||
5488d0b53a | |||
e4f84ee1c6 | |||
8e0dcd20b3 | |||
1d3e3d8c04 | |||
bc628f8d49 | |||
edd13fcc1c | |||
6a031347e5 | |||
bc14c6ee20 | |||
476f25cd81 | |||
c9d5a08ed8 | |||
f487ddbdfa | |||
d8fd1699b1 | |||
c286ffc88d | |||
1289782f06 | |||
2249f5571e | |||
e76446958c | |||
de37de912b | |||
df1a3a141b | |||
78dec87a46 | |||
ded0a3bb3d | |||
83174f2a1f | |||
c2e84ee9cc | |||
2711cb1672 | |||
ab0ba8a9d0 | |||
bd3fd3bfda | |||
260c115908 | |||
9d97201ede | |||
037b84f214 | |||
e37ee419c6 | |||
e3488fcae2 | |||
d5d5865615 | |||
29ef7d31e4 | |||
af83192d79 | |||
6952732b6c | |||
b97e5d8c2a | |||
572b077dbf | |||
72e519a4bd | |||
635e01fe74 | |||
0e72c98043 | |||
f13906f408 | |||
22ddd4b989 | |||
bb827341ed | |||
64c54703d4 | |||
1c3197b8d8 | |||
fd2292f13a | |||
4912cff65d | |||
4639843839 | |||
e29708ee57 | |||
f03a6c5325 | |||
6c3d584e67 | |||
12f4683883 | |||
874d6727e4 | |||
a9940192fb | |||
34b5418359 | |||
cbbee1b385 | |||
92a44db11c | |||
d05d42751c | |||
d32fe0f3f6 | |||
e5422db4b2 | |||
9d7b64a259 | |||
5e293bd97a | |||
4a502d9802 | |||
d384cdd397 | |||
32544b6108 | |||
39f522a13a | |||
d7d2bebf99 | |||
9427f15bef | |||
87fbc6f756 | |||
a48daa60e5 | |||
afdbbac75c | |||
e33fe14f5b | |||
e895da2ec3 | |||
e1a67b5285 | |||
a291f07e1a | |||
5d9100fa2f | |||
4faadff0a0 | |||
5d48733596 | |||
0f0b42861b | |||
209db68b28 | |||
cb4c59495e | |||
40779749ac | |||
b3cf8613b1 | |||
3fdba04924 | |||
f5f3ffac59 | |||
a38b99a188 | |||
aa31028b2c | |||
0787d69747 | |||
bae00aa8d7 | |||
388bb389e6 | |||
7e39c7e6f4 | |||
3eff8f94d2 | |||
469a4bbd35 | |||
d2ee5f2407 | |||
c264e641ea | |||
4aa0dc578b | |||
064ce6b6c0 | |||
7893d85394 | |||
5bafcb437b | |||
1530242551 | |||
eddb84cfc0 | |||
7e42c65bb6 | |||
1e6e87cb74 | |||
0c6a6e0250 | |||
1653244c69 | |||
d54e64b3c8 | |||
ec6651087d | |||
4d4b8c238d | |||
56d2b91108 | |||
89d6c959b2 | |||
76ef39fc50 | |||
af2c73772c | |||
9df0e2d1f1 | |||
5b7bbf8853 | |||
4c650057a0 | |||
2833c76f22 | |||
67feef5589 | |||
78daa50ffc | |||
87e4ea2906 | |||
c56b17029a | |||
c929684e33 | |||
f57227a148 | |||
634f707f2b | |||
406e632bd1 | |||
49f78a427b | |||
637bf3c6d9 | |||
8302c2f33b | |||
d07cc2026b | |||
4a7966c773 | |||
9b3c24b99b | |||
30bce6c14c | |||
e5512c5d57 | |||
afc571aedc | |||
ad360a62b0 | |||
3cf12d7b11 | |||
bb254d146b | |||
291ba837f2 | |||
01c631af95 | |||
4fcd4a4bd0 | |||
2346779d6b | |||
afc1a74a6c | |||
ad0eade471 | |||
28c187bcd7 | |||
91bb3aaf06 | |||
5e7bdf1b97 | |||
570453f271 | |||
ef6282fc45 | |||
e3e470bb69 | |||
67179dbc23 | |||
a5ffa735db | |||
5ee0804631 | |||
25b72fc4cd | |||
d179ec5f8b | |||
2a9e8b52de | |||
2c77562b13 | |||
98b3775297 | |||
b488df6cfe | |||
f4dfd6e30f | |||
a6e8ed15c4 | |||
de5da50910 | |||
ea2976e6d5 | |||
77d6036cd5 | |||
ccc318e807 | |||
4c8efd694c | |||
d22d11b5ff | |||
e9bab6d028 | |||
f52c09f924 | |||
118d8bccc2 | |||
6828d549e8 | |||
3fd134be03 | |||
ca7c1f89dc | |||
ca978b3d21 | |||
c1d430e61a | |||
38a85502ed | |||
241414028d | |||
29c10fbf6e | |||
4a4d72f917 | |||
b3cc9c0567 | |||
2e3352fd05 | |||
4c721e3a2f | |||
8d8ea21c69 | |||
40a7975640 | |||
cb46397dfb | |||
134904c886 | |||
bd516e3429 | |||
5efb24dd2b | |||
946498cfbc | |||
6b20863918 | |||
d306585a3f | |||
544b927933 | |||
dc94010fa5 | |||
397e8dd9d5 | |||
2b415b7af7 | |||
5d755d0a4e | |||
bade46aae5 | |||
41fbaeb3b1 | |||
8bc0adb9ba | |||
1763d0e7a2 | |||
f569c1ba28 | |||
ccd70f59ee | |||
a4969ebe06 | |||
a6ad29aa34 | |||
52eb3fe274 | |||
09e2ac5e46 | |||
df4ea0c76c | |||
718f60accb | |||
aeb5ad3ce1 | |||
2808395481 | |||
6e37c20030 | |||
3de3d76a36 | |||
07d5a86a7c | |||
ab1474ef66 | |||
918ab77baa | |||
735b1fadc3 | |||
3c531b735b | |||
f10a7e2e46 | |||
bd8177177a | |||
0302927f56 | |||
bb6842ba35 | |||
b23c8633fa | |||
b588d7f35f | |||
018d8b57f6 | |||
1987db663b | |||
a6d1ad741d | |||
e979c782d3 | |||
9e1531c1e1 | |||
0a66e4cbbb | |||
91acfe6852 | |||
b7e6bf9671 | |||
da0ea4161a | |||
7ad3015f5b | |||
f0b572da05 | |||
6d416cf674 | |||
b742f622ab | |||
02a6f16b52 | |||
294fd7df96 | |||
17c6900f83 | |||
0faa968cc3 | |||
bc784d3625 | |||
53379dfd0c | |||
76d326a2a2 | |||
c21c6b8c4b | |||
ec645ca262 | |||
ae0b0dab9f | |||
5466a5b41f | |||
9b392a683d | |||
0fa65cf329 | |||
fdd0149e71 | |||
e19a95b271 | |||
ef39274973 | |||
a7f68dcc01 | |||
1428b34299 | |||
97619e27a2 | |||
dd6cc0e6ac | |||
a939c75956 | |||
17e4b53926 | |||
a82b3680dc | |||
287342dab1 | |||
e28429ee73 | |||
49bbf3c876 | |||
5e5506f833 | |||
e06523361d | |||
ebec00138b | |||
8daa3c6834 | |||
0d418a8d58 | |||
4c66dad8c1 | |||
b1f7cca89e | |||
f90d33ca12 | |||
78c755dc48 | |||
f230170157 | |||
ced8aa8c9e | |||
77d54251f0 | |||
1632f72cbe | |||
8129b9bc46 | |||
835087dd04 | |||
4523053e6f | |||
8407f4cb24 | |||
49d244c065 | |||
3466d9c845 | |||
93d22dec35 | |||
d560ddac25 | |||
2d981bb133 | |||
d8434baa03 | |||
8f7780a4f6 | |||
2e526a664a | |||
79b79f329e | |||
287eaab3b2 | |||
a97757736b | |||
327d4b6e9e | |||
8af651be39 | |||
187bd1b8b2 | |||
4e5694fd05 | |||
82c24edcf6 | |||
ed138d794a | |||
0476a51cba | |||
c5aad69f2a | |||
5b0006476a | |||
a44238f2ef | |||
83808076e3 | |||
9660ea1bff | |||
4a9d676362 | |||
f041f128f5 | |||
e4572e5def | |||
442fa1bd46 | |||
744e66bb08 | |||
d231d68a8b | |||
01f34d0b0b | |||
9a0a24f90b | |||
430e6ac9b6 | |||
467520e7a4 | |||
5a314a5288 | |||
9989e7f4e3 | |||
31510cb437 | |||
61c9b563c2 | |||
864b9a2202 | |||
1538336836 | |||
22de5a755f | |||
355eb6d29a | |||
b8d34e0db3 | |||
e61cac687d | |||
7fbbfed8c6 | |||
c3268f4e50 | |||
34951f1999 | |||
3f52336e6c | |||
7ca8fc3b17 | |||
5289dd600b | |||
bf6529f2ce | |||
0700612249 | |||
06c005b7b0 | |||
4f6fda558c | |||
9f70b034e1 | |||
7cdb1e5e40 | |||
5f1ceebc7f | |||
1b11b000c7 | |||
83f052930a | |||
fdd8e333c1 | |||
dad1eec842 | |||
b0cde860ae | |||
e05dfb0887 | |||
dde095fab8 | |||
4a059914a9 | |||
58b6f8db48 | |||
9a777b9e1c | |||
da55e96f53 | |||
dc8f8ce4c0 | |||
f5cc77cea0 | |||
39623a7400 | |||
ae1de3a8d3 | |||
1b8a216ddf | |||
b3ad759a0a | |||
9e554999ce | |||
b34d92b051 | |||
d39ece1870 | |||
fe2d715ff1 | |||
27b306701f | |||
68837079aa | |||
c98b202eb4 | |||
587a7f6634 | |||
84e5170c9c | |||
130e412b64 | |||
0bbdca5b8b | |||
77a7244804 | |||
3e01376e08 | |||
1f92f8a9d8 | |||
8668639ae2 | |||
93fd473b8f | |||
61ad543c3e | |||
7f7504b3f5 | |||
37f599b1a1 | |||
8c2e15aed5 | |||
de71db6d42 | |||
b180b39152 | |||
24594c747e | |||
b0f2224719 | |||
749276072f | |||
f54f3d0fb7 | |||
bf1f1c73e3 | |||
b6bc6ec246 | |||
acfa95d4a8 | |||
628f830583 | |||
aa3b5aba41 | |||
639de21757 | |||
3242ddf7b5 | |||
bd1104046b | |||
81b5c4a5fd | |||
7c4eb0fbeb | |||
2d624218bf | |||
b31680990e | |||
b10ccb83e1 | |||
0497875ff3 | |||
37dc495b9d | |||
2ff4996d05 | |||
77656c672c | |||
ea5e4790bd | |||
86681c4bb0 | |||
e867768ee6 | |||
0c7f5c3ca4 | |||
e7e2ab1070 | |||
29352af369 | |||
68214bd904 | |||
bcf3fcd673 | |||
9dcf127fa5 | |||
b8b06e3e10 | |||
322b2594e0 | |||
0835f1ba3b | |||
a084495d7e | |||
fa1d4dbcfd | |||
2b2cfdac19 | |||
99744f8f8f | |||
f5fe849238 | |||
a5a9805a91 | |||
645c76a9a5 | |||
fecb2d6f0d | |||
64690d3a97 | |||
01640f44cf | |||
e37d6a9840 | |||
982ee5c699 | |||
b7b8548559 | |||
00a8cc8325 | |||
33fbb3a7eb | |||
bd6121596f | |||
702b3d1618 | |||
9d4ef94350 | |||
6c95fb3562 | |||
16b78ee629 | |||
19e008876b | |||
f2ee988105 | |||
1385050e26 | |||
03c1b110a5 | |||
cc805c6162 | |||
e01579231d | |||
34030deca9 | |||
6b26e3f2d7 | |||
d286c97753 | |||
d951f6dfe7 | |||
a23d592472 | |||
36161284e2 | |||
38f355d87b | |||
324eac1aa5 | |||
bdda4d6030 | |||
5ee1a4bc8a | |||
2dea780fbd | |||
b98e8301d5 | |||
ccd3e8bbf3 | |||
4373153800 | |||
73cfd8f325 | |||
ec384302db | |||
0a96c032b9 | |||
61e6d2e38c | |||
ea4010d704 | |||
486fff597c | |||
1e3dcbba81 | |||
f0328f8e36 | |||
895fb98456 | |||
20c708bf6d | |||
9d0c9c4bb1 | |||
2d2326a76d | |||
1ec0272303 | |||
2c81c851b2 | |||
846c644e84 | |||
13d472bd8c | |||
b71d3fe7ab | |||
847abb6f8f | |||
dda47c9466 | |||
2924622157 | |||
bda5f9a556 | |||
b1ca608bac | |||
e8efdef8de | |||
98f1c1fe87 | |||
50b18a3c10 | |||
699297520a | |||
4bb76ef0c7 | |||
ddd7caf38e | |||
10e03e695b | |||
07200f445a | |||
08a65c2282 | |||
167f8fe325 | |||
57983423c3 | |||
7bebb3e128 | |||
939fbe5567 | |||
61f81a0719 | |||
f27defc639 | |||
6a685571df | |||
87a0408c64 | |||
2b5acad4a9 | |||
655ce53383 | |||
fea712abb1 | |||
f16a118e88 | |||
041bf83d9b | |||
2299032392 | |||
f5480635c0 | |||
cdf3fa08dc | |||
38325248e5 | |||
deb901b9e4 | |||
0f98de3011 | |||
c797708fcc | |||
49b4c1e9db | |||
63a87beba4 | |||
4e0c314654 | |||
c3538a1eee | |||
2d7d0835d7 | |||
3f4cfa6c60 | |||
f8027414f5 | |||
76a47d41c8 | |||
267fdc7a1b | |||
5bced12421 | |||
674999c527 | |||
e4fea22d15 | |||
66992e90d2 | |||
e864acfdbd | |||
4cf21f58b2 | |||
d7f17613f5 | |||
e4ca37ccf6 | |||
d66fa2216e | |||
48e6dcd0f2 | |||
0ea3221d34 | |||
73c53d7833 | |||
d41be23acd | |||
2def9e4c82 | |||
ff6e46ed97 | |||
715f2b4c00 | |||
ec0cdc46f6 | |||
3e75ad9822 | |||
4f043f8e00 | |||
cc02cbc455 | |||
73c0238e3b | |||
cb122a4d03 | |||
dc69cc45d2 | |||
9d8021f0d6 | |||
9d2221b954 | |||
70a6a4bb01 | |||
e4fc856c2f | |||
3f4984fb36 | |||
d43665056d | |||
894b6fac8e | |||
59f555ad8f | |||
a7e356cc80 | |||
e758bfbae1 | |||
66381e308d | |||
295a0d84d9 | |||
1fb1c4c912 | |||
465a593536 | |||
d62f4c3bc1 | |||
a243490ad7 | |||
9e12ca6069 | |||
fbca830dd1 | |||
6146248cf4 | |||
bc26fbf1b0 | |||
97cc889374 | |||
7110b46076 | |||
c8a8464f7d | |||
8cbfb6e727 | |||
6ffc828a79 | |||
aeb6add336 | |||
5614289dd8 | |||
10c7f89351 | |||
59c268a5f8 | |||
918bb1ce8f | |||
a3ba6b4f62 | |||
8671689449 | |||
cc10b4607f | |||
981797f059 | |||
2117775747 | |||
be0e268468 | |||
fbc38b9083 | |||
8dc69b9da9 | |||
1489d3b7fa | |||
dd8c5ce442 | |||
46484da711 | |||
802ff8866e | |||
95a718992c | |||
e483a01a0f | |||
f51080916e | |||
2a952dd0b2 | |||
0de44a469a | |||
f0d0a1ebd7 | |||
569ac91e0b | |||
a11eb3a090 | |||
62010520ed | |||
987514761e | |||
ba95e36a58 | |||
abde38b4fb | |||
18400503da | |||
9de312112a | |||
8bbb0b48b4 | |||
5c0101ab2d | |||
fbe6410e53 | |||
8b9fb9fb7e | |||
0f04ec68a9 | |||
809073851f | |||
5d02041c59 | |||
4b868b8922 | |||
4061a0d35d | |||
c68bab7014 | |||
0c63d15ee9 | |||
ce19309d09 | |||
c684349195 | |||
8db14efa84 | |||
ffe414cad1 | |||
c178ab440f | |||
5befe6584d | |||
35538ca66c | |||
4f16409730 | |||
2a4e948864 | |||
d778bded95 | |||
7b4f170c94 | |||
b1d10a2884 | |||
cf2719bd12 | |||
50584f42b4 | |||
73bed45b7a | |||
6eaa507473 | |||
48dff9f3c5 | |||
a9e261599b | |||
efd8ee8a7f | |||
8615ff2acc | |||
916ed99dab | |||
e7e9578ed4 | |||
c99a259739 | |||
e0f6a2f8be | |||
ae4a3945f5 | |||
a317448bd4 | |||
0eb01b832c | |||
2a4cd6d05e | |||
9c32e73abf | |||
e5c93108e6 | |||
60c662b3a7 | |||
f66ba6508a | |||
f21268aa72 | |||
07b690a679 | |||
f9e9bf0231 | |||
c2ce7e4f07 | |||
434eb9167e | |||
09d94162f9 | |||
e6362642cf | |||
f0110e8125 | |||
479ee2a8c6 | |||
e928b6724c | |||
ec218a3ad1 | |||
c04fb91b03 | |||
323b92bffe | |||
c6d9730ddb | |||
2e95823ef7 | |||
ab2d88ca8e | |||
3abd036c47 | |||
c2f3805488 | |||
ce500635c7 | |||
0508bbc7ae | |||
c2fbadb9cf | |||
47268ecd80 | |||
9ff3b6af2e | |||
e3707ef14b | |||
45fa4ee949 | |||
9c07b37fff | |||
5b5886a993 | |||
d2bfee13e2 | |||
aff6e45e14 | |||
1b8a7f8fa0 | |||
4bc26f0a7b | |||
fc6e0fe992 | |||
cd5b5670ba | |||
6322b47e60 | |||
691a7a91a1 | |||
dd1a5ef7f9 | |||
d5c462596b | |||
47d147b65d | |||
911a75114b | |||
94b476283c | |||
252f11f85d | |||
81d5663825 | |||
68f0ac8271 | |||
b60a5c5d49 | |||
8dc2360b11 | |||
4e4b8570e5 | |||
4027557958 | |||
5c32aa411c | |||
468c243ca8 | |||
2f257b7291 | |||
6a5bb6e268 | |||
e282d963fe | |||
e2cd521b8f | |||
fba352a92a | |||
6dcc526d5b | |||
5386f1df20 | |||
ba6640b257 | |||
507a50c3ad | |||
147807c911 | |||
8b246531e6 | |||
5ad54256c5 | |||
6ebc1a967d | |||
6966db1dab | |||
2196025dd1 | |||
589f5f37b6 | |||
edd5f1445d | |||
936aba5f90 | |||
3e3a27740e | |||
b76bb79613 | |||
fbd5153487 | |||
69823bf852 | |||
d940fa679c | |||
d2bf93fe22 | |||
550ef1d29d | |||
728e4f5a8d | |||
8c2557710d | |||
a68fd44ecc | |||
e3f7f3a2e9 | |||
984232db15 | |||
78677e3fcf | |||
ad23270ec7 | |||
3c0f8c2601 | |||
989378930a | |||
dfb6546932 | |||
3aa2911579 | |||
d3f6c841c1 | |||
c1441ae4c4 | |||
dece85b31f | |||
6a1df3bd05 | |||
838400d29b | |||
b64b24ebc5 | |||
fc9087114f | |||
46c374a8a9 | |||
f0ae725721 | |||
d72d046254 | |||
edbbf7ce6c | |||
a4e4f74530 | |||
7d3de49715 | |||
a4c7d99f70 | |||
0adccaefb6 | |||
2570f1afdc | |||
ca31ca13f1 | |||
6cd666100f | |||
fe19c266ce | |||
2768af2388 | |||
dd296e1de0 | |||
0efc8a1808 | |||
df1ba52b64 | |||
221b08fd8f | |||
fa9066fe3e | |||
90ff31b38e | |||
b1f68c43b5 | |||
cd90097ca4 | |||
822eccc3b8 | |||
2ae41242a5 | |||
a1579b0ff8 | |||
ded921f0a2 | |||
3284751f43 | |||
bb738c4c26 | |||
d180c25c8f | |||
1ed842a3b4 | |||
e80aba1c2b | |||
7b8cb3135a | |||
48ae0506de | |||
096c4051b2 | |||
7610c5362a | |||
111fa5814b | |||
52fcef1e57 | |||
7d28674d3a | |||
9382b76f2a | |||
fd0b07a75a | |||
26aa34f319 | |||
44cc034651 | |||
0829cab8f2 | |||
cb7bbc6224 | |||
5079f924d6 | |||
5859e58ba1 | |||
1e747f603f | |||
9d4efd7aa2 | |||
9764f55891 | |||
4ef97b9bb2 | |||
23759b2688 | |||
4975297eb0 | |||
e8e006f4e7 | |||
c5ebecf7c6 | |||
3cf0526316 | |||
174087bfdb | |||
050f3f1f0e | |||
e90b1da367 | |||
8e10d404db | |||
7a41d61d79 | |||
4fea018b2d | |||
6957d28cfb | |||
0ea70defbe | |||
9ce5e30c06 | |||
8c29459eda | |||
a24005d5fd | |||
3651b7fca6 | |||
b64486dae9 | |||
3da71d51e0 | |||
7971ed3db3 | |||
f61ca2ec4f | |||
84e520b79c | |||
e8aa0f3315 | |||
17258e9c63 | |||
6d339e32a0 | |||
c3d05245fe | |||
b58c8ddeb4 | |||
505b3ae66f | |||
70dffc639a | |||
df828f88da | |||
42f70c8b4f | |||
10b939b096 | |||
3adfaae20c | |||
4006678577 | |||
9c5f54a723 | |||
7bab90e52d | |||
2e21e7d1f1 | |||
acb469ae2e | |||
c37c4c437e | |||
6d21fc43b9 | |||
e02aacc634 | |||
aee4512cc5 | |||
7db0e94b9e | |||
9facc119a6 | |||
a1d187113b | |||
daf650c49a | |||
8fd7e30f37 | |||
ca93be130a | |||
80e57c7a3d | |||
ae3cc844e7 | |||
585512fc7c | |||
7755cdc199 | |||
f867d08239 | |||
a103b9bc46 | |||
bb47109a78 | |||
f1ede6380b | |||
995b63adfe | |||
4023291759 | |||
86a9bc8291 | |||
b7632e9641 | |||
5281ea6ad2 | |||
d07ba49821 | |||
418659adbe | |||
1d2adef28d | |||
45518c3fe1 | |||
698d909706 | |||
81074c7048 | |||
1b4525384b | |||
6090e51b65 | |||
0827ec5aa9 | |||
4d35f94133 | |||
34cf54c2fc | |||
0565d65461 | |||
f5dc0ed342 | |||
ea06f021f8 | |||
5b49177352 | |||
f45f70af09 | |||
db86e8d5d3 | |||
55f5c9f21b | |||
e92ba1071d | |||
a7ae5666a0 | |||
24d249b0b4 | |||
e508e30800 | |||
0ee4273b04 | |||
8ca3ab2c4c | |||
18ac98700e | |||
ce4a65ffea | |||
91721caa42 | |||
1efc15c177 | |||
8ed264fcd4 | |||
4ba3f7db82 | |||
7d2723d57c | |||
24d66c5d65 | |||
229887bd40 | |||
f3f19e762f | |||
e9fa42acb8 | |||
affb35cfb9 | |||
9710c058aa | |||
1a4e892765 | |||
522de7b55d | |||
0b508fd29d | |||
1849735f12 | |||
3239c51814 | |||
78166ccd36 | |||
d8ef8a9e9b | |||
822222181b | |||
b00053247c | |||
a502decddb | |||
61065e1a5c | |||
403ae0a23b | |||
5096c0fe97 | |||
5b3f9eab94 | |||
e802cf14e3 | |||
c96d64652b | |||
dbd84697ff | |||
5013c15737 | |||
cdfcee27fb | |||
66b25ef0d0 | |||
6cb68f9496 | |||
0b635c9dc5 | |||
66afd9b33a | |||
67e070fe5a | |||
867b330966 | |||
64599973e9 | |||
35f8f03670 | |||
92a250fdb6 | |||
2f13d41e30 | |||
1e19ae8348 | |||
3810d7e4d4 | |||
5c7474e1fb | |||
1f57243b49 | |||
dc5403612e | |||
5d8ba816de | |||
a9f810c188 | |||
2582f41e12 | |||
1a7d0ba2d0 | |||
8921f613de | |||
f62e225e82 | |||
94806747df | |||
bfce29ffef | |||
8cf30aa2be | |||
f164e4c72f | |||
c57029c1f0 | |||
8fa9acb8e5 | |||
3dae25828f | |||
1c7b751d70 | |||
08dff3774c | |||
c753a2af39 | |||
a5185ec8d4 | |||
768061c85e | |||
0c136381e3 | |||
5060568f72 | |||
a09f748894 | |||
f9fa7c405f | |||
a980d3b46b | |||
87eab90ea8 | |||
75005bbcd5 | |||
19f990a9c0 | |||
bca9de9e68 | |||
0555cea5fc | |||
a93270fd60 | |||
21429c8646 | |||
0473c48f88 | |||
72d9fffd78 | |||
e79588d6c1 | |||
3a6c7d0cbd | |||
5e4c34b25a | |||
bd8a7e7ea1 | |||
f78c3e41be | |||
0a402056bc | |||
e35d41fff5 | |||
d8c25a175a | |||
e95e4659d9 | |||
e51119508c | |||
229ee34eea | |||
7fe0f3dabc | |||
38b603a42f | |||
a1e7e4a768 | |||
3f24a38840 | |||
f853d94a15 | |||
9649919d01 | |||
c8efe145b4 | |||
da80005f11 | |||
8ed163533c | |||
1760c31b52 | |||
72606f2391 | |||
c15710032e | |||
6822466aa3 | |||
64b349aee2 | |||
e3b69efbe1 | |||
322736d3bb | |||
1029119497 | |||
c1d137ed5f | |||
f6fd38bb3a | |||
c2fecb9b2a | |||
9a53a706fc | |||
91ee9c9acd | |||
67928eae28 | |||
217e7c76f1 | |||
22994c62f7 | |||
00f1450d9a | |||
3a2e89e134 | |||
c719d94473 | |||
cea3c20747 | |||
232ceabbc3 | |||
c095445394 | |||
c3d6c5fc4c | |||
0f98773164 | |||
d165d9c483 | |||
3663414053 | |||
6f2769d0b4 | |||
5f1c73db7d | |||
319346025d | |||
51a16f8c58 | |||
a00874603d | |||
941663d038 | |||
955062da2e | |||
5705bf1c71 | |||
cabce31f45 | |||
ccaae0c019 | |||
e37151021e | |||
b3495f2e4b | |||
61f296e30c | |||
ce801c9095 | |||
41fc29907a | |||
971fb584c3 | |||
af0f21da0f | |||
7cea163809 | |||
5328907f7f | |||
faaafc7e12 | |||
94edb1a71c | |||
3aaa25fa70 | |||
84bd2f1962 | |||
7f351dec27 | |||
81bd9e8ea3 | |||
10e70a06c9 | |||
f9c97de46b | |||
e0243000de | |||
ac5f95208c | |||
0e914372fb | |||
f03a35a679 | |||
e9da574147 | |||
b25a029284 | |||
c8a9d902dd | |||
6570dc3122 | |||
3851994ab0 | |||
44e186512b | |||
e7e270f511 | |||
692b82d32c | |||
c523dd584f | |||
5a32e64b05 | |||
093e2a4780 | |||
dc104cbaaa | |||
39411504a5 | |||
e3eb5ea074 | |||
734e722da4 | |||
62af25dca8 | |||
594bd8feba | |||
f2b52bba05 | |||
62df6f27cb | |||
dfdb1adea5 | |||
7967c2f874 | |||
18a0b94b0e | |||
493586cbca | |||
1d751bc617 | |||
686ff942af | |||
5b95b5d77c | |||
246bdafc74 | |||
e805543341 | |||
dba1ccc4d7 | |||
e613b3c7b1 | |||
11415bce3c | |||
9982dd0130 | |||
9fda7a36fd | |||
92847a0d11 | |||
a03842b0d5 | |||
42e88c4fcb | |||
1c855a4762 | |||
780b44bf82 | |||
8ca6e76dbc | |||
a7e986166a | |||
db9ffa1405 | |||
e926ae78ac | |||
57e266ab1d | |||
d29c7c3aca | |||
aadb38a5e6 | |||
dd0b72e1ee | |||
e52b10e33f | |||
529d88ce95 | |||
d2f2a8b0ca | |||
7a9c1ba190 | |||
6b3840b73c | |||
a38bd9ca24 | |||
91014f0171 | |||
932ab2bfca | |||
0ebdf41efa | |||
170f5c671f | |||
3d11eac784 | |||
c69e308690 | |||
25aac0bee5 | |||
b83241ff4d | |||
bd5188c811 | |||
62616b88a6 | |||
b8f85f671f | |||
7174de0d79 | |||
3785afc5a3 | |||
324415c036 | |||
1b5ccf6c13 | |||
5f39721c0a | |||
ca96acbe4f | |||
708d9509ff | |||
9328a074b1 | |||
2c077dd442 | |||
933a33a794 | |||
17f93fe084 | |||
d5e918b61f | |||
be5b4552d9 | |||
643fb0662e | |||
bef89db6e7 | |||
d45544d14e | |||
2a619d96dd | |||
8cbf544733 | |||
e051de37f3 | |||
5de459bf33 | |||
ec4f5175f1 | |||
83dd2ab919 | |||
5a8ae0bb05 | |||
39bc319b35 | |||
534bff7d31 | |||
0cda806dcc | |||
a3191a9903 | |||
fced79b0ee | |||
be887d9269 | |||
53cf073561 | |||
2972de4ba3 | |||
9ee7c2163c | |||
1a09194ae6 | |||
c18a4041f9 | |||
1cade7ef4d | |||
7413239f49 | |||
af00e4f9c9 | |||
44a26e5e21 | |||
0fbd846561 | |||
8a2bc0ab1b | |||
80505995d0 | |||
165895346c | |||
3e75d3e001 | |||
b0867f3fa0 | |||
89654cd127 | |||
f194a418f9 | |||
47c84d7974 | |||
20168147dd | |||
77c892c88e | |||
be7d72ba0d | |||
d4c504ae1c | |||
27ad6cee82 | |||
64681739fd | |||
388061674a | |||
a7320bdce9 | |||
016acebb56 | |||
07f8ffc44f | |||
466386d5cd | |||
70ef82a8e3 | |||
37eb419af2 | |||
14d8534900 | |||
c2201ce02e | |||
6efbccb387 | |||
5e0562ab51 | |||
032c83807f | |||
6655e804c4 | |||
1102d46508 | |||
45911770c5 | |||
7bd776e7db | |||
873c8451ed | |||
9f7957c073 | |||
da439f2838 | |||
eefdb379f9 | |||
2f4f49fd60 | |||
d16c4d20f8 | |||
9c56a8ce5c | |||
a8d6f309c8 | |||
ca0e38304c | |||
d16582a038 | |||
81790d726f | |||
3028344380 | |||
361911dd18 | |||
9ea183aaba | |||
8f4b918c5b | |||
4193485b43 | |||
07123e8ff3 | |||
586d63636a | |||
4045d7fea2 | |||
89c3bc585c | |||
4af9421ff0 | |||
1a398b5404 | |||
589ccc1675 | |||
c38170461d | |||
16bdd8424f | |||
b492316d5d | |||
91f4ce02b6 | |||
398343897f | |||
749ed39d79 | |||
aba72982df | |||
e8ef103799 | |||
5e23fff052 | |||
f61e71ccb9 | |||
d9f0c58c65 | |||
569c5bdbf1 | |||
eafc6a3292 | |||
dc8c943372 | |||
ac5484c4e7 | |||
746adc5e71 | |||
048d2aec27 | |||
e14bac62a0 | |||
0284a2893a | |||
07ed421521 | |||
c56d345985 | |||
dcfd40a1b8 | |||
79f11dbaa7 | |||
52aabbe7ef | |||
4423490d0b | |||
d49f206183 | |||
7a4ac9ec9c | |||
82bb4f4e89 | |||
73ca9948fe | |||
cbac016cce | |||
12252152ac | |||
5aa8d6ea21 | |||
32ec13f149 | |||
b4f4b7e21a | |||
f423e46835 | |||
c03f55ec3a | |||
23b8c24da4 | |||
7175bd8ae6 | |||
7258d1b8f3 | |||
fbc8ea8c3e | |||
6c3aa28c94 | |||
e3b5d806a8 | |||
3610f34c70 | |||
cc736e7611 | |||
789ebea863 | |||
6f8006c2ce | |||
589b93de45 | |||
db0d54f855 | |||
703960aa54 | |||
e208fbff52 | |||
572491ad1f | |||
20089c23ca | |||
c333dca065 | |||
6a2ff287b2 | |||
eedfd07f8b | |||
4ec8860783 | |||
50e716737d | |||
e022c21ddc | |||
87b691d80c | |||
c2f021692f | |||
fefaf07b6f | |||
6e568f3a08 | |||
ad6d78f2a8 | |||
a00006119f | |||
bb1c82ef7d | |||
a13b96ed88 | |||
624c58682d | |||
7ae8bd7073 | |||
bf0f107564 | |||
296e9cada2 | |||
b5fb8dd18b | |||
984a102921 | |||
4863730eca | |||
aaa0fc396b | |||
aea5d3ab00 | |||
8485027710 | |||
ec15ef7b7b | |||
bc6af7548b | |||
67662702aa | |||
9e9ad57f58 | |||
a2596d685d | |||
d8754af063 | |||
e0251fdcef | |||
b77c8190ca | |||
8428621ddc | |||
e1689cb985 | |||
939aff2936 | |||
f69786a715 | |||
b2a0d8914a | |||
acd1811c50 | |||
42a319074c | |||
a4d6cb32d1 | |||
b9aaa507fc | |||
d705d51671 | |||
02553d13a0 | |||
422236af7c | |||
abb6996f36 | |||
ee1651ce07 | |||
3bbc5799b6 | |||
63fcfc5798 | |||
db780cb119 | |||
d86e51e9c1 | |||
f05888692c | |||
1183621867 | |||
763ca9780f | |||
6cba8fe9ba | |||
fac5546321 | |||
e1ee5bf0ba | |||
0854f8c3bf | |||
9cf6c8fdc6 | |||
5f022ae081 | |||
41d4bcf0cc | |||
284297019f | |||
d4084ac5b1 | |||
d58cc8a4a8 | |||
796beaaddb | |||
e2dfd39c75 | |||
751ff64bdb | |||
c9dc41ab3f | |||
caa60b9c99 | |||
95a53e1f62 | |||
def687462c | |||
6d24116763 | |||
25f8ad13dd | |||
1d2049b53e | |||
f5b1c16e2c | |||
a1dd524b6f | |||
55a1ac5035 | |||
85793a18cd | |||
9998f66f8c | |||
cfd00af37a | |||
6274401919 | |||
2fa4655af6 | |||
8cef95f7c3 | |||
922ae4896b | |||
05ba3e7888 | |||
1daddec151 | |||
d8867a2b07 | |||
6c0125785b | |||
49ccb2e449 | |||
7ce7def515 | |||
8f455c10b0 | |||
e599fe7436 | |||
c577f72596 | |||
15d0f32773 | |||
8c63ea432c | |||
2bba420337 | |||
b656a1c133 | |||
edf98792a5 | |||
64ca334a2d | |||
be613204ab | |||
f85d5f4dac | |||
18dfa257d0 | |||
abbefd7166 | |||
ed874df293 | |||
1e018e65cb | |||
1500f00525 | |||
dafbec7553 | |||
506435bf71 | |||
f287994084 | |||
4809e2956a | |||
61fb85618c | |||
88c4bc33d2 | |||
48fe97835b | |||
119a63ab72 | |||
65a8c2c343 | |||
13b04044e5 | |||
cee50b7572 | |||
8a77e7927e | |||
1e8553d679 | |||
06bab3edb0 | |||
f66d37fb1c | |||
6fd6ecc10d | |||
c4212f9ec2 | |||
1546b9e336 | |||
17c17d1585 | |||
d09dd1a81a | |||
21c6700b83 | |||
64a0016680 | |||
9ec3bea2d6 | |||
1fb0586b06 | |||
5f26b5da28 | |||
209a1d58bf | |||
9a07973261 | |||
0d25f6fcbb | |||
ea28a63706 | |||
0d8aca8de3 | |||
4ece95a754 | |||
0028ce57b6 | |||
ece7ae5f49 | |||
621447fece | |||
a0190e4bbd | |||
b903f5c123 | |||
43c0095aa3 | |||
4873685c7e | |||
fc73e10620 | |||
92605eb4f7 | |||
9bb213e920 | |||
2ae6bca488 | |||
04335aeadb | |||
3d5125cd87 | |||
d775a20fff | |||
4dc9fd995f | |||
a79c7b4e8f | |||
195d6a5663 | |||
6c184efa75 | |||
93975c70bb | |||
53f9e7f65c | |||
768739c310 | |||
94d387cd45 | |||
721c9100f2 | |||
108498bdd0 | |||
f19d8f9655 | |||
2bf59a97c6 | |||
12a6027b40 | |||
a5d0adf395 | |||
1a23f9c274 | |||
9dbb60c4c8 | |||
ed30108918 | |||
d5893c9a13 | |||
6ee9f2ed09 | |||
631ffef438 | |||
355861f1ff | |||
ab054532ce | |||
9823675832 | |||
d4ddaaf2b1 | |||
fa0f870ac9 | |||
1dbc6bd1fc | |||
c8c9973669 | |||
65cd664195 | |||
c110c6eca7 | |||
1acfd07f1e | |||
d4885cea62 | |||
903357c853 | |||
88b84d6429 | |||
d7c4e1cb1f | |||
0d5ef5c2a6 | |||
8631694021 | |||
4133e501f3 | |||
0e6df7e511 | |||
44c926d96f | |||
75f862bf57 | |||
116914a949 | |||
689fd2ad49 | |||
7a0806a366 | |||
a81bff9349 | |||
5c876088d3 | |||
d688072990 | |||
aa1e6b0f75 | |||
26819461bd | |||
2e69e454d0 | |||
a914384bc4 | |||
41356aa00a | |||
e9a0d3141b | |||
c7d0241abb | |||
e0d6f630f8 | |||
a201c61977 | |||
4569b4dbd6 | |||
c9bb5468b6 | |||
b26e76efe9 | |||
c7739536e8 | |||
dfa631e64a | |||
18e3f16576 | |||
72d51e0b1e | |||
6ccb5673c4 | |||
a82026746d | |||
9d36a5a3b0 | |||
81e2dac955 | |||
5940431e2d | |||
15008bf05d | |||
06045cb4a5 | |||
9492b9ff03 | |||
0d9870dd9e | |||
5e70843a73 | |||
d25e07660e | |||
579736f210 | |||
8650c57ccd | |||
9071daebe9 | |||
fcaf8a0cdc | |||
231c16df25 | |||
971359997f | |||
f7abf9fd0f | |||
e68836c123 | |||
dc1e36fa0e | |||
7bb1b50835 | |||
96c28f748d | |||
e160c2b79a | |||
e2a332e5df | |||
ebff26f8f1 | |||
559739ec1d | |||
45a1509455 | |||
5b106083e7 | |||
2d8a7ed086 | |||
b18f783fed | |||
b6c0cf9683 | |||
d00b43c592 | |||
cc9b051d12 | |||
d67eb8af2f | |||
3400e0d609 | |||
f76583276a | |||
31a4f0ab05 | |||
744c2824c5 | |||
140f11190b | |||
fb67a2f660 | |||
d2b65f77e8 | |||
03cdd4ca50 | |||
e5e4fb370c | |||
70d93078e3 | |||
b344bd9582 | |||
add6bcca3e | |||
836415b215 | |||
7ce503e6a0 | |||
a4538ac1e8 | |||
6dfdada493 | |||
b7fe7dfad1 | |||
9c865560fd | |||
1788883262 | |||
90730e706b | |||
041725e9d0 | |||
c5fe3eb87e | |||
b2a8141373 | |||
408c84cd42 | |||
6825ac9ccf | |||
f3d6d3a8f2 | |||
8eaad79de7 | |||
131d446183 | |||
4042198262 | |||
729491eab7 | |||
3a04ebf14f | |||
b64d13a357 | |||
e0ac51cbd9 | |||
4e279e2a70 | |||
a3929b719a | |||
63be22d5dc | |||
f171d1f3d6 | |||
c33f3e281b | |||
59d0efd6d6 | |||
56b6c05bf9 | |||
8dd48923d8 | |||
7dac3c44f5 | |||
4a0d9dbc30 | |||
c916ab3319 | |||
f9ee5f4b43 | |||
e817bb9cc4 | |||
5946029585 | |||
f4b5aeaed2 | |||
5a6a58576a | |||
90accff030 | |||
49f6771ca9 | |||
fa5ebd27d4 | |||
4cddbfa7cf | |||
f1cd5413e9 | |||
7f0d8184c9 | |||
e569297d6a | |||
bea282dae9 | |||
17544b1824 | |||
afd67497de | |||
5e1576f79f | |||
848ab63a2a | |||
c7d9d79ad2 | |||
9ae2ac2fb7 | |||
125bb0f19a | |||
bfdca8b576 | |||
da3467b7f9 | |||
fb5350543c | |||
ccbc891992 | |||
b69e6dcead | |||
4d8cee2d48 | |||
f68771abe4 | |||
581afee039 | |||
e49a4e0aff | |||
753e9a04c0 | |||
18c69c998d | |||
6cfe43539a | |||
c61043450b | |||
e4f7d0d311 | |||
d8e246daaf | |||
620f999e80 | |||
dcbb6b1e4d | |||
a99891e7a5 | |||
52f89065e1 | |||
9ffe98c00e | |||
63d7f3d116 | |||
c052cf11b9 | |||
0c901efb16 | |||
030cccfeef | |||
062e3bd757 | |||
5174b8ca28 | |||
090de29ddf | |||
59607f5e99 | |||
763d1fe6a3 | |||
7d6622942c | |||
0867c1b447 | |||
6883d6e724 | |||
8f4b8edb8b | |||
5324d385c0 | |||
fc505b5447 | |||
3c3d6b3d2a | |||
2a05a87fe7 | |||
3ecad55910 | |||
d929fdeb9b | |||
9d577cae6f | |||
6e820841d4 | |||
82d6813e7c | |||
2f12a32c20 | |||
6178bc6f8e | |||
209748f128 | |||
f64ea6235f | |||
80d288b146 | |||
6500f161f7 | |||
abd93d824a | |||
2b2aa8926f | |||
21b8279cfe | |||
cd7f6c1fda | |||
70e44a8e7f | |||
b8c6192a61 | |||
c7b7141b11 | |||
c57f8f563b | |||
016af73d19 | |||
f961683516 | |||
27a1b787c8 | |||
6a79a3279b | |||
734d3bf175 | |||
8c8877df5a | |||
754be04f11 | |||
1adee75e1c | |||
a4b611a3d3 | |||
220bb764e5 | |||
a750114a38 | |||
4d56c5ce4c | |||
c0ad9a888b | |||
0598d84f61 | |||
b09c83504e | |||
3133925ab2 | |||
03b9485cb3 | |||
ded46cc1b6 | |||
c1a4cd0fa7 | |||
797d72133e | |||
56cfd9f4ce | |||
e0e8f717a0 | |||
cfb25b0e80 | |||
e489f879c3 | |||
c0e926109e | |||
b05a02b91c | |||
d411aa165e | |||
be961bae0f | |||
0a4acc9656 | |||
d3fe82c55b | |||
a659d8079e | |||
00fda9b25a | |||
4aeeb49d23 | |||
fee52f79b8 | |||
4952e79e45 | |||
95c0fb5075 | |||
62ac1d98a4 | |||
5aa5116edc | |||
5e7ab2f4ea | |||
b52d0bd9d4 | |||
0fb474be3a | |||
3019a8c1fd | |||
7ee287d3d9 | |||
ae6368c985 | |||
bb7a80b1ab | |||
8474a6232d | |||
22f61140d1 | |||
52ee516cf3 | |||
ef7bbfff87 | |||
891e9117f6 | |||
3fc3ecd184 | |||
06f3abe26d | |||
50f14f225c | |||
b732455fd3 | |||
daf74ae0b1 | |||
4af764d040 | |||
97e9ed11bc | |||
f55bf55d43 | |||
e604d5347f | |||
979e70f10d | |||
b203beb61d | |||
28a303ddd4 | |||
3943803412 | |||
7185e96da1 | |||
251a0869be | |||
804ad8f4df | |||
8b0504825d | |||
77d3761b50 | |||
0525a03a69 | |||
1a5a1708b7 | |||
4797ca025e | |||
3121ebd044 | |||
b098306839 | |||
a721319e3a | |||
29726cefb8 | |||
811964502d | |||
0629d76bb0 | |||
016714c57b | |||
c679352517 | |||
1042ddda0f | |||
d998b5d038 | |||
bf6651d1ca | |||
1cba0aea27 | |||
a9d2e826fe | |||
c47318afa8 | |||
ecd52bc610 | |||
5c76d107cb | |||
98c1f76f24 | |||
e7f4566dd4 | |||
e0179ee190 | |||
e567fe6960 | |||
7c60db1e24 | |||
0ea73df717 | |||
aa7bb101ed | |||
3bc97a5564 | |||
c2ffa14684 | |||
535dbdd1c8 | |||
00d56e06ef | |||
7e94fcb680 | |||
6ced4230f4 |
8
.clang-format
Normal file
8
.clang-format
Normal file
@ -0,0 +1,8 @@
|
||||
# Run manually to reformat a file:
|
||||
# clang-format -i --style=file <file>
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentCaseLabels: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
DerivePointerAlignment: false
|
6
.github/pull_request_template.md
vendored
Normal file
6
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +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.
|
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,18 +1,36 @@
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
*.iml
|
||||
.idea/
|
||||
.externalNativeBuild/
|
||||
.gradle/
|
||||
gradle/
|
||||
gradlew*
|
||||
local.properties
|
||||
build/
|
||||
|
||||
bin/
|
||||
/_CPack_Packages
|
||||
/CMakeScripts
|
||||
/doc/doxyxml
|
||||
/doc/html
|
||||
/doc/virtualenv
|
||||
/doc/node_modules
|
||||
virtualenv
|
||||
/Testing
|
||||
/install_manifest.txt
|
||||
*~
|
||||
*.a
|
||||
*.so*
|
||||
*.xcodeproj
|
||||
*.zip
|
||||
cmake_install.cmake
|
||||
CPack*Config.cmake
|
||||
CPack*.cmake
|
||||
fmt-*.cmake
|
||||
CTestTestfile.cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
FMT.build
|
||||
Makefile
|
||||
run-msbuild.bat
|
||||
fmt.pc
|
||||
|
103
.travis.yml
103
.travis.yml
@ -1,24 +1,101 @@
|
||||
language: cpp
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
os: linux
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: |-
|
||||
Gsnp9ERFnXt+diCfc7Vb72g+7HDn1MCHvw4zfUDdoBh9bxxFlLQRlzZZfwWhzni57lflrt
|
||||
0QHXafu+oBVOJuNv6WauV3+ZyuWIQRmNGjZFNLvZsXHK/dyad2vGQBPvEkb+8l/aCyTpbr
|
||||
6pxmyzLHSn1ZR7OX5rfPvwM3tOyZ3H0=
|
||||
matrix:
|
||||
- BUILD=Doc
|
||||
- BUILD=Debug
|
||||
- BUILD=Release
|
||||
a1eovNn4uol9won7ghr67eD3/59oeESN+G9bWE+ecI1V6yRseG9whniGhIpC/YfMW/Qz5I
|
||||
5sxSmFjaw9bxCISNwUIrL1O5x2AmRYTnFcXk4dFsUvlZg+WeF/aKyBYCNRM8C2ndbBmtAO
|
||||
o1F2EwFbiso0EmtzhAPs19ujiVxkLn4=
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: BUILD=Doc
|
||||
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
|
||||
|
364
CMakeLists.txt
364
CMakeLists.txt
@ -1,64 +1,146 @@
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
# Use newer policies if available, up to most recent tested version of CMake.
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.11)
|
||||
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}")
|
||||
endif ()
|
||||
|
||||
# Joins arguments and places the results in ${result_var}.
|
||||
function(join result_var)
|
||||
set(result )
|
||||
foreach (arg ${ARGN})
|
||||
set(result "${result}${arg}")
|
||||
endforeach ()
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# 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})
|
||||
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 (NOT CMAKE_BUILD_TYPE)
|
||||
set(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.")
|
||||
if (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.")
|
||||
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.")
|
||||
|
||||
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." ON)
|
||||
option(FMT_INSTALL "Generate the install target." ON)
|
||||
option(FMT_TEST "Generate the test target." ON)
|
||||
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_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)
|
||||
|
||||
project(FORMAT)
|
||||
# Get version from core.h
|
||||
file(READ include/fmt/core.h core_h)
|
||||
if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
|
||||
message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.")
|
||||
endif ()
|
||||
# Use math to skip leading zeros if any.
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
|
||||
${CPACK_PACKAGE_VERSION_PATCH})
|
||||
message(STATUS "Version: ${FMT_VERSION}")
|
||||
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-std=c++11 HAVE_STD_CPP11_FLAG)
|
||||
if (HAVE_STD_CPP11_FLAG)
|
||||
# Check if including cmath works with -std=c++11 and -O3.
|
||||
# It may not in MinGW due to bug http://ehc.ac/p/mingw/bugs/2250/.
|
||||
set(CMAKE_REQUIRED_FLAGS "-std=c++11 -O3")
|
||||
check_cxx_source_compiles("
|
||||
#include <cmath>
|
||||
int main() {}" FMT_CPP11_CMATH)
|
||||
# Check if including <unistd.h> works with -std=c++11.
|
||||
# It may not in MinGW due to bug http://sourceforge.net/p/mingw/bugs/2024/.
|
||||
check_cxx_source_compiles("
|
||||
#include <unistd.h>
|
||||
int main() {}" FMT_CPP11_UNISTD_H)
|
||||
if (FMT_CPP11_CMATH AND FMT_CPP11_UNISTD_H)
|
||||
set(CPP11_FLAG -std=c++11)
|
||||
else ()
|
||||
check_cxx_compiler_flag(-std=gnu++11 HAVE_STD_GNUPP11_FLAG)
|
||||
if (HAVE_STD_CPP11_FLAG)
|
||||
set(CPP11_FLAG -std=gnu++11)
|
||||
endif ()
|
||||
endif ()
|
||||
set(CMAKE_REQUIRED_FLAGS )
|
||||
else ()
|
||||
check_cxx_compiler_flag(-std=c++0x HAVE_STD_CPP0X_FLAG)
|
||||
if (HAVE_STD_CPP0X_FLAG)
|
||||
set(CPP11_FLAG -std=c++0x)
|
||||
endif ()
|
||||
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
|
||||
if (CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(JoinPaths)
|
||||
|
||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||
if (${index} GREATER -1)
|
||||
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
||||
# compatibility with older CMake versions.
|
||||
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
||||
endif ()
|
||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||
|
||||
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
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Woverloaded-virtual
|
||||
-Wconversion -Wswitch-enum
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept
|
||||
-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)
|
||||
endif ()
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||
-Wnull-dereference -Wduplicated-cond)
|
||||
endif ()
|
||||
set(WERROR_FLAG -Werror)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion
|
||||
-Wno-sign-conversion -Wdeprecated -Wweak-vtables)
|
||||
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||
if (HAS_NULLPTR_WARNING)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
-Wzero-as-null-pointer-constant)
|
||||
endif ()
|
||||
set(WERROR_FLAG -Werror)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
set(PEDANTIC_COMPILE_FLAGS /W3)
|
||||
set(WERROR_FLAG /WX)
|
||||
endif ()
|
||||
|
||||
if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
# If Microsoft SDK is installed create script run-msbuild.bat that
|
||||
# calls SetEnv.cmd to to set up build environment and runs msbuild.
|
||||
# calls SetEnv.cmd to set up build environment and runs msbuild.
|
||||
# It is useful when building Visual Studio projects with the SDK
|
||||
# toolchain rather than Visual Studio.
|
||||
include(FindSetEnv)
|
||||
@ -66,64 +148,163 @@ if (CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"")
|
||||
endif ()
|
||||
# Set FrameworkPathOverride to get rid of MSB3644 warnings.
|
||||
set(netfxpath "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0")
|
||||
join(netfxpath
|
||||
"C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\"
|
||||
".NETFramework\\v4.0")
|
||||
file(WRITE run-msbuild.bat "
|
||||
${MSBUILD_SETUP}
|
||||
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
|
||||
endif ()
|
||||
|
||||
set(FMT_SOURCES format.cc format.h)
|
||||
set(strtod_l_headers stdlib.h)
|
||||
if (APPLE)
|
||||
set(strtod_l_headers ${strtod_l_headers} xlocale.h)
|
||||
endif ()
|
||||
|
||||
include(CheckSymbolExists)
|
||||
if (WIN32)
|
||||
check_symbol_exists(open io.h HAVE_OPEN)
|
||||
check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
else ()
|
||||
check_symbol_exists(open fcntl.h HAVE_OPEN)
|
||||
endif ()
|
||||
if (HAVE_OPEN)
|
||||
add_definitions(-DFMT_USE_FILE_DESCRIPTORS=1)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} posix.cc posix.h)
|
||||
check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
endif ()
|
||||
|
||||
if (CPP11_FLAG)
|
||||
set(CMAKE_REQUIRED_FLAGS ${CPP11_FLAG})
|
||||
function(add_headers VAR)
|
||||
set(headers ${${VAR}})
|
||||
foreach (header ${ARGN})
|
||||
set(headers ${headers} include/fmt/${header})
|
||||
endforeach()
|
||||
set(${VAR} ${headers} PARENT_SCOPE)
|
||||
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)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
else()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
endif ()
|
||||
|
||||
if (BIICODE)
|
||||
include(support/cmake/biicode.cmake)
|
||||
return()
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
|
||||
if (HAVE_STRTOD_L)
|
||||
target_compile_definitions(fmt PUBLIC FMT_LOCALE)
|
||||
endif ()
|
||||
|
||||
add_library(cppformat ${FMT_SOURCES})
|
||||
if (BUILD_SHARED_LIBS AND UNIX AND NOT APPLE)
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(cppformat -Wl,--as-needed)
|
||||
endif ()
|
||||
if (FMT_PEDANTIC AND
|
||||
(CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")))
|
||||
set(FMT_EXTRA_COMPILE_FLAGS "-Wall -Wextra -Wshadow -pedantic")
|
||||
if (MINGW)
|
||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||
endif ()
|
||||
|
||||
# If FMT_PEDANTIC is TRUE, then test compilation with both -std=c++11
|
||||
# and the default flags. Otherwise use only the default flags.
|
||||
# The library is distributed in the source form and users have full control
|
||||
# over compile options, so the options used here only matter for testing.
|
||||
if (CPP11_FLAG AND FMT_PEDANTIC)
|
||||
set(FMT_EXTRA_COMPILE_FLAGS "${FMT_EXTRA_COMPILE_FLAGS} ${CPP11_FLAG}")
|
||||
set(FMT_TEST_DEFAULT_FLAGS TRUE)
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
||||
endif ()
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
|
||||
set_target_properties(cppformat
|
||||
PROPERTIES COMPILE_FLAGS "${FMT_EXTRA_COMPILE_FLAGS}")
|
||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
set(CPPFORMAT_VERSION 2.0.0)
|
||||
if (NOT CPPFORMAT_VERSION MATCHES "^([0-9]+).([0-9]+).([0-9]+)$")
|
||||
message(FATAL_ERROR "Invalid version format ${CPPFORMAT_VERSION}.")
|
||||
target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
|
||||
|
||||
set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
|
||||
|
||||
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
|
||||
# property because it's not set by default.
|
||||
set(FMT_LIB_NAME fmt)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
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)
|
||||
endif ()
|
||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||
endif ()
|
||||
if (FMT_SAFE_DURATION_CAST)
|
||||
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
||||
endif()
|
||||
|
||||
add_library(fmt-header-only INTERFACE)
|
||||
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
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
# Install targets.
|
||||
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.")
|
||||
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.")
|
||||
|
||||
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.")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
${version_config}
|
||||
VERSION ${FMT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
|
||||
join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}")
|
||||
join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}")
|
||||
|
||||
configure_file(
|
||||
"${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
|
||||
"${pkgconfig}"
|
||||
@ONLY)
|
||||
configure_package_config_file(
|
||||
${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
|
||||
${project_config}
|
||||
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||
|
||||
set(INSTALL_TARGETS fmt fmt-header-only)
|
||||
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||
# imported targets.
|
||||
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
||||
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||
|
||||
# Install version, config and target files.
|
||||
install(
|
||||
FILES ${project_config} ${version_config}
|
||||
DESTINATION ${FMT_CMAKE_DIR})
|
||||
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")
|
||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
||||
endif ()
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
|
||||
if (FMT_DOC)
|
||||
add_subdirectory(doc)
|
||||
@ -134,13 +315,17 @@ if (FMT_TEST)
|
||||
add_subdirectory(test)
|
||||
endif ()
|
||||
|
||||
set_target_properties(cppformat PROPERTIES
|
||||
VERSION ${CPPFORMAT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR})
|
||||
# Control fuzzing independent of the unit tests.
|
||||
if (FMT_FUZZ)
|
||||
add_subdirectory(test/fuzzing)
|
||||
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
||||
endif ()
|
||||
|
||||
if (EXISTS .gitignore)
|
||||
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||
if (MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
# Get the list of ignored files from .gitignore.
|
||||
file (STRINGS ".gitignore" lines)
|
||||
LIST(REMOVE_ITEM lines /doc/html)
|
||||
file (STRINGS ${gitignore} lines)
|
||||
list(REMOVE_ITEM lines /doc/html)
|
||||
foreach (line ${lines})
|
||||
string(REPLACE "." "[.]" line "${line}")
|
||||
string(REPLACE "*" ".*" line "${line}")
|
||||
@ -151,15 +336,8 @@ if (EXISTS .gitignore)
|
||||
|
||||
set(CPACK_SOURCE_GENERATOR ZIP)
|
||||
set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME cppformat-${CPPFORMAT_VERSION})
|
||||
set(CPACK_RESOURCE_FILE_README ${FORMAT_SOURCE_DIR}/README.rst)
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
|
||||
set(CPACK_PACKAGE_NAME fmt)
|
||||
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
|
||||
include(CPack)
|
||||
endif ()
|
||||
|
||||
# Install targets.
|
||||
if (FMT_INSTALL)
|
||||
set(FMT_LIB_DIR lib CACHE STRING
|
||||
"Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
install(TARGETS cppformat DESTINATION ${FMT_LIB_DIR})
|
||||
install(FILES format.h DESTINATION include/cppformat)
|
||||
endif ()
|
||||
|
17
CONTRIBUTING.md
Normal file
17
CONTRIBUTING.md
Normal file
@ -0,0 +1,17 @@
|
||||
Contributing to {fmt}
|
||||
=====================
|
||||
|
||||
By submitting a pull request or a patch, you represent that you have the right
|
||||
to license your contribution to the {fmt} project owners and the community,
|
||||
agree that your contributions are licensed under the {fmt} license, and agree
|
||||
to future changes to the licensing.
|
||||
|
||||
All C++ code must adhere to [Google C++ Style Guide](
|
||||
https://google.github.io/styleguide/cppguide.html) with the following
|
||||
exceptions:
|
||||
|
||||
* Exceptions are permitted
|
||||
* snake_case should be used instead of UpperCamelCase for function and type
|
||||
names
|
||||
|
||||
Thanks for contributing!
|
2780
ChangeLog.rst
2780
ChangeLog.rst
File diff suppressed because it is too large
Load Diff
42
LICENSE.rst
42
LICENSE.rst
@ -1,23 +1,27 @@
|
||||
Copyright (c) 2012 - 2015, Victor Zverovich
|
||||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
|
||||
All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY 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.
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
||||
|
696
README.rst
696
README.rst
@ -1,269 +1,177 @@
|
||||
C++ Format
|
||||
==========
|
||||
{fmt}
|
||||
=====
|
||||
|
||||
.. image:: https://travis-ci.org/cppformat/cppformat.png?branch=master
|
||||
:target: https://travis-ci.org/cppformat/cppformat
|
||||
.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master
|
||||
:target: https://travis-ci.org/fmtlib/fmt
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/qk0bhyhqp1ekpat8
|
||||
:target: https://ci.appveyor.com/project/vitaut/cppformat
|
||||
|
||||
.. image:: https://badges.gitter.im/Join%20Chat.svg
|
||||
:alt: Join the chat at https://gitter.im/cppformat/cppformat
|
||||
:target: https://gitter.im/cppformat/cppformat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
C++ Format is an open-source formatting library for C++.
|
||||
It can be used as a safe alternative to printf or as a fast
|
||||
alternative to IOStreams.
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
|
||||
:alt: fmt is continuously fuzzed att 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
|
||||
|
||||
`Documentation <http://cppformat.github.io/latest/>`_
|
||||
.. 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.
|
||||
|
||||
`Documentation <https://fmt.dev/latest/>`__
|
||||
|
||||
Q&A: ask questions on `StackOverflow with the tag fmt
|
||||
<https://stackoverflow.com/questions/tagged/fmt>`_.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Two APIs: faster concatenation-based write API and slower (but still
|
||||
very fast) replacement-based format API with positional arguments for
|
||||
localization.
|
||||
* Write API similar to the one used by IOStreams but stateless allowing
|
||||
faster implementation.
|
||||
* Format API with `format string syntax
|
||||
<http://cppformat.github.io/latest/syntax.html>`_
|
||||
similar to the one used by `str.format
|
||||
<http://docs.python.org/2/library/stdtypes.html#str.format>`_ in Python.
|
||||
* Simple `format API <https://fmt.dev/dev/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 <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||
* Safe `printf implementation
|
||||
<http://cppformat.github.io/latest/reference.html#printf-formatting-functions>`_
|
||||
including the POSIX extension for positional arguments.
|
||||
* Support for user-defined types.
|
||||
* High speed: performance of the format API is close to that of
|
||||
glibc's `printf <http://en.cppreference.com/w/cpp/io/c/fprintf>`_
|
||||
and better than performance of IOStreams. See `Speed tests`_ and
|
||||
`Fast integer to string conversion in C++
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
* Small code size both in terms of source code (format consists of a single
|
||||
header file and a single source file) and compiled code.
|
||||
See `Compile time and code bloat`_.
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
|
||||
the POSIX extension for positional arguments
|
||||
* Extensibility: support for 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
|
||||
<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/cppformat/cppformat/tree/master/test>`_.
|
||||
* Safety: the library is fully type safe, errors in format strings are
|
||||
reported using exceptions, automatic memory management prevents buffer
|
||||
overflow errors.
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors
|
||||
* Ease of use: small self-contained code base, no external dependencies,
|
||||
permissive BSD `license
|
||||
<https://github.com/cppformat/cppformat/blob/master/LICENSE.rst>`_
|
||||
* `Portability <http://cppformat.github.io#portability>`_ with consistent output
|
||||
across platforms and support for older compilers.
|
||||
permissive MIT `license
|
||||
<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).
|
||||
* Support for wide strings.
|
||||
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro.
|
||||
(``-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 <http://cppformat.github.io/latest/>`_ for more details.
|
||||
See the `documentation <https://fmt.dev/latest/>`_ for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
This prints ``Hello, world!`` to stdout:
|
||||
Print ``Hello, world!`` to ``stdout``:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("Hello, {}!", "world"); // uses Python-like format string syntax
|
||||
fmt::printf("Hello, %s!", "world"); // uses printf format string syntax
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main() {
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
|
||||
Arguments can be accessed by position and arguments' indices can be repeated:
|
||||
Format a string:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("{0}{1}{0}", "abra", "cad");
|
||||
// s == "abracadabra"
|
||||
std::string s = fmt::format("The answer is {}.", 42);
|
||||
// s == "The answer is 42."
|
||||
|
||||
C++ Format can be used as a safe portable replacement for ``itoa``:
|
||||
Format a string using positional arguments:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::MemoryWriter w;
|
||||
w << 42; // replaces itoa(42, buffer, 10)
|
||||
w << fmt::hex(42); // replaces itoa(42, buffer, 16)
|
||||
// access the string using w.str() or w.c_str()
|
||||
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||
// s == "I'd rather be happy than right."
|
||||
|
||||
An object of any user-defined type for which there is an overloaded
|
||||
:code:`std::ostream` insertion operator (``operator<<``) can be formatted:
|
||||
Print a chrono duration:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Date &d) {
|
||||
return os << d.year_ << '-' << d.month_ << '-' << d.day_;
|
||||
int main() {
|
||||
using namespace std::chrono_literals;
|
||||
fmt::print("Elapsed time: {}", 42ms);
|
||||
}
|
||||
|
||||
prints "Elapsed time: 42ms".
|
||||
|
||||
Check a format string at compile time:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
// test.cc
|
||||
#include <fmt/format.h>
|
||||
std::string s = format(FMT_STRING("{:d}"), "hello");
|
||||
|
||||
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));
|
||||
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
|
||||
// s == "The date is 2012-12-9"
|
||||
|
||||
You can use the `FMT_VARIADIC
|
||||
<http://cppformat.github.io/latest/reference.html#utilities>`_
|
||||
macro to create your own functions similar to `format
|
||||
<http://cppformat.github.io/latest/reference.html#format>`_ and
|
||||
`print <http://cppformat.github.io/latest/reference.html#print>`_
|
||||
which take arbitrary arguments:
|
||||
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 report_error(const char *format, fmt::ArgList args) {
|
||||
void vreport_error(const char* format, fmt::format_args args) {
|
||||
fmt::print("Error: ");
|
||||
fmt::print(format, args);
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
template <typename... Args>
|
||||
void report_error(const char* format, const Args & ... args) {
|
||||
vreport_error(format, fmt::make_format_args(args...));
|
||||
}
|
||||
FMT_VARIADIC(void, report_error, const char *)
|
||||
|
||||
report_error("file not found: {}", path);
|
||||
|
||||
Note that you only need to define one function that takes ``fmt::ArgList``
|
||||
argument. ``FMT_VARIADIC`` automatically defines necessary wrappers that
|
||||
accept variable number of arguments.
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <http://play0ad.com/>`_: A free, open-source, cross-platform real-time strategy game
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/HarpyWar/pvpgn>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
* `KBEngine <http://www.kbengine.org/>`_: An open-source MMOG server engine
|
||||
|
||||
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
|
||||
|
||||
* `PenUltima Online (POL) <http://www.polserver.com/>`_:
|
||||
An MMO server, compatible with most Ultima Online clients
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance, associative database
|
||||
|
||||
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
|
||||
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster proxy
|
||||
|
||||
* `Saddy <https://code.google.com/p/saddy/>`_:
|
||||
Small crossplatform 2D graphic engine
|
||||
|
||||
* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
|
||||
|
||||
* `Stellar <https://www.stellar.org/>`_: Financial platform
|
||||
|
||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
|
||||
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source MMORPG framework
|
||||
|
||||
`More... <https://github.com/search?q=cppformat&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||
`issue <https://github.com/cppformat/cppformat/issues>`_.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
||||
So why yet another formatting library?
|
||||
|
||||
There are plenty of methods for doing this task, from standard ones like
|
||||
the printf family of function and IOStreams to Boost Format library and
|
||||
FastFormat. The reason for creating a new library is that every existing
|
||||
solution that I found either had serious issues or didn't provide
|
||||
all the features I needed.
|
||||
|
||||
Printf
|
||||
~~~~~~
|
||||
|
||||
The good thing about printf is that it is pretty fast and readily available
|
||||
being a part of the C standard library. The main drawback is that it
|
||||
doesn't support user-defined types. Printf also has safety issues although
|
||||
they are mostly solved with `__attribute__ ((format (printf, ...))
|
||||
<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||
There is a POSIX extension that adds positional arguments required for
|
||||
`i18n <http://en.wikipedia.org/wiki/Internationalization_and_localization>`_
|
||||
to printf but it is not a part of C99 and may not be available on some
|
||||
platforms.
|
||||
|
||||
IOStreams
|
||||
~~~~~~~~~
|
||||
|
||||
The main issue with IOStreams is best illustrated with an example:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
|
||||
|
||||
which is a lot of typing compared to printf:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
printf("%.2f\n", 1.23456);
|
||||
|
||||
Matthew Wilson, the author of FastFormat, referred to this situation with
|
||||
IOStreams as "chevron hell". IOStreams doesn't support positional arguments
|
||||
by design.
|
||||
|
||||
The good part is that IOStreams supports user-defined types and is safe
|
||||
although error reporting is awkward.
|
||||
|
||||
Boost Format library
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is a very powerful library which supports both printf-like format
|
||||
strings and positional arguments. The main its drawback is performance.
|
||||
According to 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`_).
|
||||
|
||||
FastFormat
|
||||
~~~~~~~~~~
|
||||
|
||||
This is an interesting library which is fast, safe and has positional
|
||||
arguments. However it has significant limitations, citing its author:
|
||||
|
||||
Three features that have no hope of being accommodated within the
|
||||
current design are:
|
||||
|
||||
* Leading zeros (or any other non-space padding)
|
||||
* Octal/hexadecimal encoding
|
||||
* Runtime width/alignment specification
|
||||
|
||||
It is also quite big and has a heavy dependency, STLSoft, which might be
|
||||
too restrictive for using it in some projects.
|
||||
|
||||
Loki SafeFormat
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
SafeFormat is a formatting library which uses printf-like format strings
|
||||
and is type safe. It doesn't support user-defined types or positional
|
||||
arguments. It makes unconventional use of ``operator()`` for passing
|
||||
format arguments.
|
||||
|
||||
Tinyformat
|
||||
~~~~~~~~~~
|
||||
|
||||
This library supports printf-like format strings and is very small and
|
||||
fast. Unfortunately it doesn't support positional arguments and wrapping
|
||||
it in C++98 is somewhat difficult. Also its performance and code compactness
|
||||
are limited by IOStreams.
|
||||
|
||||
Boost Spirit.Karma
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is not really a formatting library but I decided to include it here
|
||||
for completeness. As IOStreams it suffers from the problem of mixing
|
||||
verbatim text with arguments. The library is pretty fast, but slower
|
||||
on integer formatting than ``fmt::Writer`` on Karma's own benchmark,
|
||||
see `Fast integer to string conversion in C++
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
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.
|
||||
|
||||
Benchmarks
|
||||
----------
|
||||
@ -271,76 +179,80 @@ Benchmarks
|
||||
Speed tests
|
||||
~~~~~~~~~~~
|
||||
|
||||
The following speed tests results were generated by building
|
||||
``tinyformat_test.cpp`` on Ubuntu GNU/Linux 14.04.1 with
|
||||
``g++-4.8.2 -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of three
|
||||
runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` or
|
||||
equivalent is filled 2000000 times with output sent to ``/dev/null``; for
|
||||
further details see the `source
|
||||
<https://github.com/cppformat/format-benchmark/blob/master/tinyformat_test.cpp>`_.
|
||||
|
||||
================= ============= ===========
|
||||
Library Method Run Time, s
|
||||
================= ============= ===========
|
||||
EGLIBC 2.19 printf 1.30
|
||||
libstdc++ 4.8.2 std::ostream 1.85
|
||||
C++ Format 1.0 fmt::print 1.42
|
||||
tinyformat 2.0.1 tfm::printf 2.25
|
||||
Boost Format 1.54 boost::format 9.94
|
||||
libc printf 1.04
|
||||
libc++ std::ostream 3.05
|
||||
{fmt} 6.1.1 fmt::print 0.75
|
||||
Boost Format 1.67 boost::format 7.24
|
||||
Folly Format folly::format 2.23
|
||||
================= ============= ===========
|
||||
|
||||
As you can see ``boost::format`` is much slower than the alternative methods; this
|
||||
is confirmed by `other tests <http://accu.org/index.php/journals/1539>`_.
|
||||
Tinyformat is quite good coming close to IOStreams. Unfortunately tinyformat
|
||||
cannot be faster than the IOStreams because it uses them internally.
|
||||
Performance of cppformat is close to that of printf, being `faster than printf on integer
|
||||
formatting <http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_,
|
||||
but slower on floating-point formatting which dominates this benchmark.
|
||||
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
||||
|
||||
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
||||
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||
or equivalent is filled 2,000,000 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>`_.
|
||||
|
||||
{fmt} is up to 10x 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>`_:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
|
||||
:target: https://fmt.dev/unknown_mac64_clang10.0.html
|
||||
|
||||
Compile time and code bloat
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The script `bloat-test.py
|
||||
<https://github.com/cppformat/format-benchmark/blob/master/bloat-test.py>`_
|
||||
from `format-benchmark <https://github.com/cppformat/format-benchmark>`_
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
|
||||
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
||||
tests compile time and code bloat for nontrivial projects.
|
||||
It generates 100 translation units and uses ``printf()`` or its alternative
|
||||
five times in each to simulate a medium sized project. The resulting
|
||||
executable size and compile time (g++-4.8.1, Ubuntu GNU/Linux 13.10,
|
||||
best of three) is shown in the following tables.
|
||||
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
||||
macOS Sierra, best of three) is shown in the following tables.
|
||||
|
||||
**Optimized build (-O3)**
|
||||
|
||||
============ =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============ =============== ==================== ==================
|
||||
printf 2.6 41 30
|
||||
IOStreams 19.4 92 70
|
||||
C++ Format 46.8 46 34
|
||||
tinyformat 64.6 418 386
|
||||
Boost Format 222.8 990 923
|
||||
============ =============== ==================== ==================
|
||||
============= =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============= =============== ==================== ==================
|
||||
printf 2.6 29 26
|
||||
printf+string 16.4 29 26
|
||||
iostreams 31.1 59 55
|
||||
{fmt} 19.0 37 34
|
||||
Boost Format 91.9 226 203
|
||||
Folly Format 115.7 101 88
|
||||
============= =============== ==================== ==================
|
||||
|
||||
As you can see, C++ Format has two times less overhead in terms of resulting
|
||||
code size compared to IOStreams and comes pretty close to ``printf``.
|
||||
Boost Format has by far the largest overheads.
|
||||
As you can see, {fmt} has 60% less overhead in terms of resulting binary code
|
||||
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
||||
and Folly Format have the largest overheads.
|
||||
|
||||
``printf+string`` is the same as ``printf`` but with extra ``<string>``
|
||||
include to measure the overhead of the latter.
|
||||
|
||||
**Non-optimized build**
|
||||
|
||||
============ =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============ =============== ==================== ==================
|
||||
printf 2.1 41 30
|
||||
IOStreams 19.7 86 62
|
||||
C++ Format 47.9 108 86
|
||||
tinyformat 27.7 234 190
|
||||
Boost Format 122.6 884 763
|
||||
============ =============== ==================== ==================
|
||||
============= =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============= =============== ==================== ==================
|
||||
printf 2.2 33 30
|
||||
printf+string 16.0 33 30
|
||||
iostreams 28.3 56 52
|
||||
{fmt} 18.2 59 50
|
||||
Boost Format 54.1 365 303
|
||||
Folly Format 79.9 445 430
|
||||
============= =============== ==================== ==================
|
||||
|
||||
``libc``, ``libstdc++`` and ``libformat`` are all linked as shared
|
||||
libraries to compare formatting function overhead only. Boost Format
|
||||
and tinyformat are header-only libraries so they don't provide any
|
||||
linkage options.
|
||||
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
||||
compare formatting function overhead only. Boost Format is a
|
||||
header-only library so it doesn't provide any linkage options.
|
||||
|
||||
Running the tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@ -348,14 +260,14 @@ Running the tests
|
||||
Please refer to `Building the library`__ for the instructions on how to build
|
||||
the library and run the unit tests.
|
||||
|
||||
__ http://cppformat.github.io/latest/usage.html#building-the-library
|
||||
__ https://fmt.dev/latest/usage.html#building-the-library
|
||||
|
||||
Benchmarks reside in a separate repository,
|
||||
`format-benchmarks <https://github.com/cppformat/format-benchmark>`_,
|
||||
`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
|
||||
so to run the benchmarks you first need to clone this repository and
|
||||
generate Makefiles with CMake::
|
||||
|
||||
$ git clone --recursive https://github.com/cppformat/format-benchmark.git
|
||||
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
|
||||
$ cd format-benchmark
|
||||
$ cmake .
|
||||
|
||||
@ -367,46 +279,216 @@ or the bloat test::
|
||||
|
||||
$ make bloat-test
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform
|
||||
real-time strategy game
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
|
||||
* `Aseprite <https://github.com/aseprite/aseprite>`_:
|
||||
Animated sprite editor & pixel art tool
|
||||
|
||||
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft
|
||||
operations suite
|
||||
|
||||
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space
|
||||
|
||||
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: A compiler cache
|
||||
|
||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database management system
|
||||
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
* `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
|
||||
(Lyft)
|
||||
|
||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||
|
||||
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
||||
|
||||
* `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
|
||||
|
||||
* `Keypirinha <https://keypirinha.com/>`_: A semantic launcher for Windows
|
||||
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
|
||||
|
||||
* `Knuth <https://kth.cash/>`_: High-performance Bitcoin full-node
|
||||
|
||||
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
||||
Research programming language for concurrent ownership
|
||||
|
||||
* `MongoDB <https://mongodb.com/>`_: Distributed document database
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `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
|
||||
|
||||
* `PyTorch <https://github.com/pytorch/pytorch>`_: An open-source machine
|
||||
learning library
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
|
||||
associative database
|
||||
|
||||
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
|
||||
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
|
||||
proxy
|
||||
|
||||
* `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
|
||||
library
|
||||
|
||||
* `Salesforce Analytics Cloud
|
||||
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `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++
|
||||
framework for high-performance server applications on modern hardware
|
||||
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
|
||||
|
||||
* `Stellar <https://www.stellar.org/>`_: Financial platform
|
||||
|
||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
|
||||
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
||||
MMORPG framework
|
||||
|
||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: The new Windows
|
||||
Terminal
|
||||
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||
`issue <https://github.com/fmtlib/fmt/issues>`_.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
||||
So why yet another formatting library?
|
||||
|
||||
There are plenty of methods for doing this task, from standard ones like
|
||||
the printf family of function and iostreams to Boost Format and FastFormat
|
||||
libraries. The reason for creating a new library is that every existing
|
||||
solution that I found either had serious issues or didn't provide
|
||||
all the features I needed.
|
||||
|
||||
printf
|
||||
~~~~~~
|
||||
|
||||
The good thing about ``printf`` is that it is pretty fast and readily available
|
||||
being a part of the C standard library. The main drawback is that it
|
||||
doesn't support user-defined types. ``printf`` also has safety issues although
|
||||
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
|
||||
<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||
There is a POSIX extension that adds positional arguments required for
|
||||
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
|
||||
to ``printf`` but it is not a part of C99 and may not be available on some
|
||||
platforms.
|
||||
|
||||
iostreams
|
||||
~~~~~~~~~
|
||||
|
||||
The main issue with iostreams is best illustrated with an example:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
|
||||
|
||||
which is a lot of typing compared to printf:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
printf("%.2f\n", 1.23456);
|
||||
|
||||
Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
|
||||
don't support positional arguments by design.
|
||||
|
||||
The good part is that iostreams support user-defined types and are safe although
|
||||
error handling is awkward.
|
||||
|
||||
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
|
||||
Format also has excessive build times and severe code bloat issues (see
|
||||
`Benchmarks`_).
|
||||
|
||||
FastFormat
|
||||
~~~~~~~~~~
|
||||
|
||||
This is an interesting library which is fast, safe and has positional arguments.
|
||||
However, it has significant limitations, citing its author:
|
||||
|
||||
Three features that have no hope of being accommodated within the
|
||||
current design are:
|
||||
|
||||
* Leading zeros (or any other non-space padding)
|
||||
* Octal/hexadecimal encoding
|
||||
* Runtime width/alignment specification
|
||||
|
||||
It is also quite big and has a heavy dependency, STLSoft, which might be too
|
||||
restrictive for using it in some projects.
|
||||
|
||||
Boost Spirit.Karma
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is not really a formatting library but I decided to include it here for
|
||||
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
||||
with arguments. The library is pretty fast, but slower on integer formatting
|
||||
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
|
||||
see `Converting a hundred million integers to strings per second
|
||||
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
C++ Format is distributed under the BSD `license
|
||||
<https://github.com/cppformat/cppformat/blob/master/LICENSE.rst>`_.
|
||||
{fmt} is distributed under the MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||
|
||||
The `Format String Syntax
|
||||
<http://cppformat.github.io/latest/syntax.html>`_
|
||||
Documentation License
|
||||
---------------------
|
||||
|
||||
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
|
||||
section in the documentation is based on the one from Python `string module
|
||||
documentation <http://docs.python.org/3/library/string.html#module-string>`_
|
||||
adapted for the current library. For this reason the documentation is
|
||||
distributed under the Python Software Foundation license available in
|
||||
`doc/python-license.txt
|
||||
<https://raw.github.com/cppformat/cppformat/master/doc/python-license.txt>`_.
|
||||
It only applies if you distribute the documentation of C++ Format.
|
||||
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
|
||||
For this reason the documentation is distributed under the Python Software
|
||||
Foundation license available in `doc/python-license.txt
|
||||
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||
It only applies if you distribute the documentation of {fmt}.
|
||||
|
||||
Links
|
||||
-----
|
||||
Maintainers
|
||||
-----------
|
||||
|
||||
`API changes/compatibility report <http://upstream-tracker.org/versions/cppformat.html>`_
|
||||
|
||||
Acknowledgments
|
||||
---------------
|
||||
|
||||
The benchmark section of this readme file and the performance tests are taken
|
||||
from the excellent `tinyformat <https://github.com/c42f/tinyformat>`_ library
|
||||
written by Chris Foster. Boost Format library is acknowledged transitively
|
||||
since it had some influence on tinyformat.
|
||||
Some ideas used in the implementation are borrowed from `Loki
|
||||
<http://loki-lib.sourceforge.net/>`_ SafeFormat and `Diagnostic API
|
||||
<http://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
|
||||
`Clang <http://clang.llvm.org/>`_.
|
||||
Format string syntax and the documentation are based on Python's `str.format
|
||||
<http://docs.python.org/2/library/stdtypes.html#str.format>`_.
|
||||
Thanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable
|
||||
comments and contribution to the design of the type-safe API and
|
||||
`Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary
|
||||
formatting. Thanks `Ruslan Baratov <https://github.com/ruslo>`_ for comprehensive
|
||||
`comparison of integer formatting algorithms <https://github.com/ruslo/int-dec-format-tests>`_
|
||||
and useful comments regarding performance, `Boris Kaul <https://github.com/localvoid>`_ for
|
||||
`C++ counting digits benchmark <https://github.com/localvoid/cxx-benchmark-count-digits>`_.
|
||||
Thanks to `CarterLi <https://github.com/CarterLi>`_ for contributing various
|
||||
improvements to the code.
|
||||
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
||||
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
|
||||
<https://github.com/foonathan>`_) with contributions from many other people.
|
||||
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
||||
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
|
||||
Let us know if your contribution is not listed or mentioned incorrectly and
|
||||
we'll make it right.
|
||||
|
@ -5,7 +5,9 @@ if (NOT DOXYGEN)
|
||||
endif ()
|
||||
|
||||
add_custom_target(doc
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${CPPFORMAT_VERSION})
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION}
|
||||
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
||||
DESTINATION share/doc/cppformat)
|
||||
DESTINATION share/doc/fmt OPTIONAL
|
||||
PATTERN ".doctrees" EXCLUDE)
|
||||
|
67
doc/_templates/layout.html
vendored
67
doc/_templates/layout.html
vendored
@ -1,25 +1,27 @@
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
<meta name="description" content="Small, safe and fast formatting library for C++">
|
||||
<meta name="description" content="Small, safe and fast formatting library">
|
||||
<meta name="keywords" content="C++, formatting, printf, string, library">
|
||||
<meta name="author" content="Victor Zverovich">
|
||||
<link rel="stylesheet" href="_static/cppformat.css">
|
||||
<link rel="stylesheet" href="_static/fmt.css">
|
||||
{# Google Analytics #}
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20116650-4"></script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-20116650-4', 'cppformat.github.io');
|
||||
ga('send', 'pageview');
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-20116650-4');
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{%- macro searchform(classes, button) %}
|
||||
<form class="{{classes}}" role="search" action="{{ pathto('search') }}" method="get">
|
||||
<form class="{{classes}}" role="search" action="{{ pathto('search') }}"
|
||||
method="get">
|
||||
<div class="form-group">
|
||||
<input type="text" name="q" class="form-control" {{ 'placeholder="Search"' if not button }} >
|
||||
<input type="text" name="q" class="form-control"
|
||||
{{ 'placeholder="Search"' if not button }} >
|
||||
</div>
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
@ -36,30 +38,33 @@
|
||||
<div class="navbar-content">
|
||||
{# Brand and toggle get grouped for better mobile display #}
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<button type="button" class="navbar-toggle collapsed"
|
||||
data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="index.html">C++ Format</a>
|
||||
<a class="navbar-brand" href="index.html">{fmt}</a>
|
||||
</div>
|
||||
|
||||
{# Collect the nav links, forms, and other content for toggling #}
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
{# TODO: update versions automatically #}
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||
aria-expanded="false">{{ version }} <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
|
||||
role="button" aria-expanded="false">{{ version }}
|
||||
<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="http://cppformat.github.io/1.1.0/">1.1.0</a></li>
|
||||
<li><a href="http://cppformat.github.io/1.0.0/">1.0.0</a></li>
|
||||
{% for v in versions.split(',') %}
|
||||
<li><a href="https://fmt.dev/{{v}}">{{v}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% for name in ['Contents', 'Usage', 'API', 'Syntax'] %}
|
||||
{% if pagename == name.lower() %}
|
||||
<li class="active"><a href="{{name.lower()}}.html">{{name}} <span class="sr-only">(current)</span></a></li>
|
||||
<li class="active"><a href="{{name.lower()}}.html">{{name}}
|
||||
<span class="sr-only">(current)</span></a></li>
|
||||
{%else%}
|
||||
<li><a href="{{name.lower()}}.html">{{name}}</a></li>
|
||||
{%endif%}
|
||||
@ -74,20 +79,25 @@
|
||||
</div> {# /.tb-container #}
|
||||
</nav>
|
||||
{% if pagename == "index" %}
|
||||
{% set download_url = 'https://github.com/fmtlib/fmt/releases/download' %}
|
||||
<div class="jumbotron">
|
||||
<div class="tb-container">
|
||||
<h1>C++ Format</h1>
|
||||
<p class="lead">Small, safe and fast formatting library for C++</p>
|
||||
<h1>{fmt}</h1>
|
||||
<p class="lead">A modern formatting library</p>
|
||||
<div class="btn-group" role="group">
|
||||
{% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||
<a class="btn btn-success"
|
||||
href="https://github.com/cppformat/cppformat/releases/download/2.0.0/cppformat-2.0.0.zip">
|
||||
<span class="glyphicon glyphicon-download"></span> Download
|
||||
href="{{download_url}}/{{version}}/{{name}}-{{version}}.zip">
|
||||
<span class="glyphicon glyphicon-download"></span> Download
|
||||
</a>
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<button type="button" class="btn btn-success dropdown-toggle"
|
||||
data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="https://github.com/cppformat/cppformat/releases/download/2.0.0/cppformat-2.0.0.zip">Version 2.0.0</a></li>
|
||||
<li><a href="https://github.com/cppformat/cppformat/releases/download/1.1.0/cppformat-1.1.0.zip">Version 1.1.0</a></li>
|
||||
<li><a href="https://github.com/cppformat/cppformat/releases/download/1.0.0/cppformat-1.0.0.zip">Version 1.0.0</a></li>
|
||||
{% for v in versions.split(',') %}
|
||||
{% set name = 'fmt' if v.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||
<li><a href="{{download_url}}/{{v}}/{{name}}-{{v}}.zip">Version {{v}}
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -104,14 +114,15 @@
|
||||
{% block content %}
|
||||
<div class="tb-container">
|
||||
<div class="row">
|
||||
{# TODO: integrate sidebar
|
||||
{# Sidebar is currently disabled.
|
||||
<div class="bs-sidebar">
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}"
|
||||
alt="Logo"/>
|
||||
</a></p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
454
doc/api.rst
454
doc/api.rst
@ -4,126 +4,452 @@
|
||||
API Reference
|
||||
*************
|
||||
|
||||
All functions and classes provided by the C++ Format library reside
|
||||
in namespace ``fmt`` and macros have prefix ``FMT_``. For brevity the
|
||||
namespace is usually omitted in examples.
|
||||
The {fmt} library API consists of the following parts:
|
||||
|
||||
Formatting functions
|
||||
====================
|
||||
* :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/chrono.h <chrono-api>`: date and time formatting
|
||||
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
||||
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>` similar
|
||||
to the one used by Python's `str.format
|
||||
<http://docs.python.org/3/library/stdtypes.html#str.format>`_ function.
|
||||
All functions and types provided by the library reside in namespace ``fmt`` and
|
||||
macros have prefix ``FMT_``.
|
||||
|
||||
.. _core-api:
|
||||
|
||||
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``.
|
||||
|
||||
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.
|
||||
|
||||
*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.
|
||||
arguments in the resulting string. A function taking *format_str* doesn't
|
||||
participate in an overload resolution if the latter is not a string.
|
||||
|
||||
*args* is an argument list representing arbitrary arguments.
|
||||
*args* is an argument list representing objects to be formatted.
|
||||
|
||||
.. _format:
|
||||
|
||||
.. doxygenfunction:: format(CStringRef, ArgList)
|
||||
|
||||
.. doxygenfunction:: operator""_format(const char *, std::size_t)
|
||||
.. doxygenfunction:: format(const S&, Args&&...)
|
||||
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<type_identity_t<Char>>>)
|
||||
|
||||
.. _print:
|
||||
|
||||
.. doxygenfunction:: print(CStringRef, ArgList)
|
||||
.. doxygenfunction:: print(const S&, Args&&...)
|
||||
.. doxygenfunction:: vprint(string_view, format_args)
|
||||
|
||||
.. doxygenfunction:: print(std::FILE *, CStringRef, ArgList)
|
||||
.. doxygenfunction:: print(std::FILE *, const S&, Args&&...)
|
||||
.. doxygenfunction:: vprint(std::FILE *, string_view, format_args)
|
||||
|
||||
.. doxygenfunction:: print(std::ostream&, CStringRef, ArgList)
|
||||
Named Arguments
|
||||
---------------
|
||||
|
||||
Printf formatting functions
|
||||
===========================
|
||||
.. doxygenfunction:: fmt::arg(const S&, const T&)
|
||||
|
||||
The following functions use `printf format string syntax
|
||||
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||
a POSIX extension for positional arguments.
|
||||
Named arguments are not supported in compile-time checks at the moment.
|
||||
|
||||
.. doxygenfunction:: printf(CStringRef, ArgList)
|
||||
Argument Lists
|
||||
--------------
|
||||
|
||||
.. doxygenfunction:: fprintf(std::FILE*, CStringRef, ArgList)
|
||||
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||
|
||||
.. doxygenfunction:: sprintf(CStringRef, ArgList)
|
||||
|
||||
Write API
|
||||
=========
|
||||
|
||||
.. doxygenclass:: fmt::BasicWriter
|
||||
.. doxygenclass:: fmt::format_arg_store
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicMemoryWriter
|
||||
.. doxygenclass:: fmt::dynamic_format_arg_store
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicArrayWriter
|
||||
.. doxygenclass:: fmt::basic_format_args
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: bin
|
||||
.. doxygenstruct:: fmt::format_args
|
||||
|
||||
.. doxygenfunction:: oct
|
||||
.. doxygenclass:: fmt::basic_format_arg
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: hex
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
.. doxygenfunction:: hexu
|
||||
.. doxygenclass:: fmt::basic_string_view
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: pad(int, unsigned, Char)
|
||||
.. doxygentypedef:: fmt::string_view
|
||||
.. doxygentypedef:: fmt::wstring_view
|
||||
|
||||
Locale
|
||||
------
|
||||
|
||||
All formatting is locale-independent by default. Use the ``'n'`` format
|
||||
specifier to insert the appropriate number separator characters from the
|
||||
locale::
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <locale>
|
||||
|
||||
std::locale::global(std::locale("en_US.UTF-8"));
|
||||
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
|
||||
|
||||
.. _format-api:
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Formatting User-defined Types
|
||||
-----------------------------
|
||||
|
||||
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
|
||||
template and implement ``parse`` and ``format`` methods::
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
struct point { double x, y; };
|
||||
|
||||
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
|
||||
// [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
|
||||
//
|
||||
// fmt::format("{:f} - point of interest", point{1, 2});
|
||||
//
|
||||
// the range will contain "f} - point of interest". The formatter should
|
||||
// parse specifiers until '}' or the end of the range. In this example
|
||||
// the formatter should parse the 'f' specifier and return an iterator
|
||||
// pointing to '}'.
|
||||
|
||||
// Parse the presentation format and store it in the formatter:
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
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");
|
||||
|
||||
// Return an iterator past the end of the parsed range:
|
||||
return it;
|
||||
}
|
||||
|
||||
// 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
|
||||
// ctx.out() is an output iterator to write to.
|
||||
return format_to(
|
||||
ctx.out(),
|
||||
presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})",
|
||||
p.x, p.y);
|
||||
}
|
||||
};
|
||||
|
||||
Then you can pass objects of type ``point`` to any formatting function::
|
||||
|
||||
point p = {1, 2};
|
||||
std::string s = fmt::format("{:f}", p);
|
||||
// s == "(1.0, 2.0)"
|
||||
|
||||
You can also reuse existing formatters via inheritance or composition, for
|
||||
example::
|
||||
|
||||
enum class color {red, green, blue};
|
||||
|
||||
template <> struct fmt::formatter<color>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(color c, FormatContext& ctx) {
|
||||
string_view name = "unknown";
|
||||
switch (c) {
|
||||
case color::red: name = "red"; break;
|
||||
case color::green: name = "green"; break;
|
||||
case color::blue: name = "blue"; break;
|
||||
}
|
||||
return formatter<string_view>::format(name, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
|
||||
all string format specifications, for example
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
fmt::format("{:>10}", color::blue)
|
||||
|
||||
will return ``" blue"``.
|
||||
|
||||
You can also write a formatter for a hierarchy of classes::
|
||||
|
||||
#include <type_traits>
|
||||
#include <fmt/format.h>
|
||||
|
||||
struct A {
|
||||
virtual ~A() {}
|
||||
virtual std::string name() const { return "A"; }
|
||||
};
|
||||
|
||||
struct B : A {
|
||||
virtual std::string name() const { return "B"; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
|
||||
fmt::formatter<std::string> {
|
||||
template <typename FormatCtx>
|
||||
auto format(const A& a, FormatCtx& ctx) {
|
||||
return fmt::formatter<std::string>::format(a.name(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
B b;
|
||||
A& a = b;
|
||||
fmt::print("{}", a); // prints "B"
|
||||
}
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_parse_context
|
||||
:members:
|
||||
|
||||
Output Iterator Support
|
||||
-----------------------
|
||||
|
||||
.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...)
|
||||
.. doxygenfunction:: fmt::format_to_n(OutputIt, std::size_t, string_view, 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""_a(const char *, size_t)
|
||||
|
||||
Utilities
|
||||
=========
|
||||
---------
|
||||
|
||||
.. doxygenfunction:: fmt::arg(StringRef, const T&)
|
||||
.. doxygenstruct:: fmt::is_char
|
||||
|
||||
.. doxygenfunction:: operator""_a(const char *, std::size_t)
|
||||
.. doxygentypedef:: fmt::char_t
|
||||
|
||||
.. doxygendefine:: FMT_CAPTURE
|
||||
.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...)
|
||||
|
||||
.. doxygendefine:: FMT_VARIADIC
|
||||
.. doxygenfunction:: fmt::to_string(const T&)
|
||||
|
||||
.. doxygenclass:: fmt::ArgList
|
||||
.. doxygenfunction:: fmt::to_wstring(const T&)
|
||||
|
||||
.. doxygenfunction:: fmt::to_string_view(const Char *)
|
||||
|
||||
.. doxygenfunction:: fmt::join(const Range&, string_view)
|
||||
|
||||
.. doxygenfunction:: fmt::join(It, It, string_view)
|
||||
|
||||
.. doxygenclass:: fmt::detail::buffer
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicStringRef
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicCStringRef
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::Buffer
|
||||
.. doxygenclass:: fmt::basic_memory_buffer
|
||||
:protected-members:
|
||||
:members:
|
||||
|
||||
System Errors
|
||||
=============
|
||||
-------------
|
||||
|
||||
.. doxygenclass:: fmt::SystemError
|
||||
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:
|
||||
|
||||
.. doxygenclass:: fmt::WindowsError
|
||||
.. doxygenfunction:: fmt::format_system_error
|
||||
|
||||
.. doxygenclass:: fmt::windows_error
|
||||
:members:
|
||||
|
||||
.. _formatstrings:
|
||||
|
||||
Custom allocators
|
||||
=================
|
||||
Custom Allocators
|
||||
-----------------
|
||||
|
||||
The C++ Format library supports custom dynamic memory allocators.
|
||||
The {fmt} library supports custom dynamic memory allocators.
|
||||
A custom allocator class can be specified as a template argument to
|
||||
:class:`fmt::BasicMemoryWriter`::
|
||||
:class:`fmt::basic_memory_buffer`::
|
||||
|
||||
typedef fmt::BasicMemoryWriter<char, CustomAllocator> CustomMemoryWriter;
|
||||
using custom_memory_buffer =
|
||||
fmt::basic_memory_buffer<char, fmt::inline_buffer_size, custom_allocator>;
|
||||
|
||||
It is also possible to write a formatting function that uses a custom
|
||||
allocator::
|
||||
|
||||
typedef std::basic_string<char, std::char_traits<char>, CustomAllocator> CustomString;
|
||||
using custom_string =
|
||||
std::basic_string<char, std::char_traits<char>, custom_allocator>;
|
||||
|
||||
CustomString format(CustomAllocator alloc, fmt::CStringRef format_str,
|
||||
fmt::ArgList args) {
|
||||
CustomMemoryWriter writer(alloc);
|
||||
writer.write(format_str, args);
|
||||
return CustomString(writer.data(), writer.size(), alloc);
|
||||
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);
|
||||
return custom_string(buf.data(), buf.size(), alloc);
|
||||
}
|
||||
FMT_VARIADIC(CustomString, format, CustomAllocator, fmt::CStringRef)
|
||||
|
||||
template <typename ...Args>
|
||||
inline custom_string format(custom_allocator alloc,
|
||||
fmt::string_view format_str,
|
||||
const Args& ... args) {
|
||||
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.
|
||||
|
||||
.. _ranges-api:
|
||||
|
||||
Ranges and Tuple Formatting
|
||||
===========================
|
||||
|
||||
The library also supports convenient formatting of ranges and tuples::
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
std::tuple<char, int, float> t{'a', 1, 2.0f};
|
||||
// Prints "('a', 1, 2.0)"
|
||||
fmt::print("{}", t);
|
||||
|
||||
|
||||
NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main
|
||||
``format.h`` header, but expect this to change in the future.
|
||||
|
||||
Using ``fmt::join``, you can separate tuple elements with a custom separator::
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
// Prints "1, a"
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
|
||||
.. _chrono-api:
|
||||
|
||||
Date and Time Formatting
|
||||
========================
|
||||
|
||||
The library supports `strftime
|
||||
<http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like date and time
|
||||
formatting::
|
||||
|
||||
#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));
|
||||
|
||||
The format string syntax is described in the documentation of
|
||||
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
|
||||
|
||||
.. _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.
|
||||
|
||||
.. doxygendefine:: FMT_COMPILE
|
||||
|
||||
.. _ostream-api:
|
||||
|
||||
``std::ostream`` Support
|
||||
========================
|
||||
|
||||
``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
|
||||
user-defined types that have overloaded ``operator<<``::
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
class date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
date(int year, int month, int day): year_(year), month_(month), day_(day) {}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const date& d) {
|
||||
return os << 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"
|
||||
|
||||
.. doxygenfunction:: print(std::basic_ostream<Char>&, const S&, Args&&...)
|
||||
|
||||
.. _printf-api:
|
||||
|
||||
``printf`` Formatting
|
||||
=====================
|
||||
|
||||
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
|
||||
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:: fprintf(std::FILE *, const S&, const Args&...)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::basic_ostream<Char>&, const S&, const Args&...)
|
||||
|
||||
.. doxygenfunction:: sprintf(const S&, const Args&...)
|
||||
|
||||
Compatibility with C++20 ``std::format``
|
||||
========================================
|
||||
|
||||
{fmt} implements nearly all of the `C++20 formatting library
|
||||
<https://en.cppreference.com/w/cpp/utility/format>`_ with the following
|
||||
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.
|
||||
|
@ -90,7 +90,8 @@
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
HAS_SOURCE: {{ has_source|lower }},
|
||||
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
|
||||
};
|
||||
</script>
|
||||
{%- for scriptfile in script_files %}
|
||||
|
89
doc/build.py
89
doc/build.py
@ -6,32 +6,39 @@ import errno, os, shutil, sys, tempfile
|
||||
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
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']
|
||||
|
||||
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:
|
||||
check_version = kwargs.get('check_version', '')
|
||||
#output = check_output(['pip', 'show', package.split('/')[1]])
|
||||
#if check_version in output:
|
||||
# print('{} already installed'.format(package))
|
||||
# return
|
||||
package = 'git+git://github.com/{0}.git@{1}'.format(package, commit)
|
||||
print('Installing {}'.format(package))
|
||||
check_call(['pip', 'install', '--upgrade', package])
|
||||
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
|
||||
print('Installing {0}'.format(package))
|
||||
check_call(['pip', 'install', package])
|
||||
|
||||
def build_docs(version='dev'):
|
||||
def create_build_env(dirname='virtualenv'):
|
||||
# Create virtualenv.
|
||||
doc_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
virtualenv_dir = 'virtualenv'
|
||||
check_call(['virtualenv', virtualenv_dir])
|
||||
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(virtualenv_dir, scripts_dir,
|
||||
'activate_this.py')
|
||||
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.
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
pip_version = get_distribution('pip').version
|
||||
if LooseVersion(pip_version) < LooseVersion('1.5.4'):
|
||||
print("Updating pip")
|
||||
@ -46,52 +53,72 @@ def build_docs(version='dev'):
|
||||
except DistributionNotFound:
|
||||
pass
|
||||
# Install Sphinx and Breathe.
|
||||
pip_install('cppformat/sphinx',
|
||||
'12dde8afdb0a7bb5576e2656692c3478c69d8cc3',
|
||||
check_version='1.4a0.dev-20151013')
|
||||
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee',
|
||||
min_version='1.4.1.dev20160531')
|
||||
pip_install('michaeljones/breathe',
|
||||
'1c9d7f80378a92cffa755084823a78bb38ee4acc')
|
||||
'129222318f7c8f865d2631e7da7b033567e7f56a',
|
||||
min_version='4.2.0')
|
||||
|
||||
def build_docs(version='dev', **kwargs):
|
||||
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
|
||||
work_dir = kwargs.get('work_dir', '.')
|
||||
include_dir = kwargs.get(
|
||||
'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt'))
|
||||
# Build docs.
|
||||
cmd = ['doxygen', '-']
|
||||
p = Popen(cmd, stdin=PIPE)
|
||||
doxyxml_dir = os.path.join(work_dir, 'doxyxml')
|
||||
p.communicate(input=r'''
|
||||
PROJECT_NAME = C++ Format
|
||||
PROJECT_NAME = fmt
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_MAN = NO
|
||||
GENERATE_RTF = NO
|
||||
CASE_SENSE_NAMES = NO
|
||||
INPUT = {0}/format.h
|
||||
INPUT = {0}/core.h {0}/compile.h {0}/format.h {0}/os.h \
|
||||
{0}/ostream.h {0}/printf.h {0}/time.h
|
||||
QUIET = YES
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
AUTOLINK_SUPPORT = NO
|
||||
GENERATE_HTML = NO
|
||||
GENERATE_XML = YES
|
||||
XML_OUTPUT = doxyxml
|
||||
XML_OUTPUT = {1}
|
||||
ALIASES = "rst=\verbatim embed:rst"
|
||||
ALIASES += "endrst=\endverbatim"
|
||||
MACRO_EXPANSION = YES
|
||||
PREDEFINED = _WIN32=1 \
|
||||
FMT_USE_VARIADIC_TEMPLATES=1 \
|
||||
FMT_USE_RVALUE_REFERENCES=1 \
|
||||
FMT_USE_USER_DEFINED_LITERALS=1
|
||||
FMT_USE_USER_DEFINED_LITERALS=1 \
|
||||
FMT_USE_ALIAS_TEMPLATES=1 \
|
||||
FMT_API= \
|
||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||
"FMT_END_NAMESPACE=}}" \
|
||||
"FMT_STRING_ALIAS=1" \
|
||||
"FMT_ENABLE_IF(B)="
|
||||
EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str
|
||||
'''.format(os.path.dirname(doc_dir)).encode('UTF-8'))
|
||||
'''.format(include_dir, doxyxml_dir).encode('UTF-8'))
|
||||
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',
|
||||
'-Dbreathe_projects.format=' + os.path.join(os.getcwd(), 'doxyxml'),
|
||||
'-Dversion=' + version, '-Drelease=' + version, '-Aversion=' + version,
|
||||
'-b', 'html', doc_dir, 'html'])
|
||||
'-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',
|
||||
'--include-path=' + os.path.join(doc_dir, 'bootstrap'),
|
||||
os.path.join(doc_dir, 'cppformat.less'),
|
||||
'html/_static/cppformat.css'])
|
||||
os.path.join(doc_dir, 'fmt.less'),
|
||||
os.path.join(html_dir, '_static', 'fmt.css')])
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
print('lessc not found; make sure that Less (http://lesscss.org/) is installed')
|
||||
print('lessc not found; make sure that Less (http://lesscss.org/) ' +
|
||||
'is installed')
|
||||
sys.exit(1)
|
||||
return 'html'
|
||||
return html_dir
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_build_env()
|
||||
build_docs(sys.argv[1])
|
||||
|
15
doc/conf.py
15
doc/conf.py
@ -46,8 +46,8 @@ source_suffix = '.rst'
|
||||
#master_doc = 'contents'
|
||||
|
||||
# General information about the project.
|
||||
project = u'C++ Format'
|
||||
copyright = u'2012-2015, Victor Zverovich'
|
||||
project = u'fmt'
|
||||
copyright = u'2012-present, Victor Zverovich'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@ -76,7 +76,7 @@ copyright = u'2012-2015, Victor Zverovich'
|
||||
exclude_patterns = ['virtualenv']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
default_role = 'cpp:any'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
@ -198,7 +198,7 @@ latex_elements = {
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'format.tex', u'C++ Format Documentation',
|
||||
('index', 'format.tex', u'fmt documentation',
|
||||
u'Victor Zverovich', 'manual'),
|
||||
]
|
||||
|
||||
@ -228,8 +228,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'format', u'format Documentation',
|
||||
[u'Victor Zverovich'], 1)
|
||||
('index', 'fmt', u'fmt documentation', [u'Victor Zverovich'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@ -242,8 +241,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'format', u'format Documentation',
|
||||
u'Victor Zverovich', 'format', 'One line description of project.',
|
||||
('index', 'fmt', u'fmt documentation',
|
||||
u'Victor Zverovich', 'fmt', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
@ -59,3 +59,8 @@ div.sphinxsidebar {
|
||||
p.rubric {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.github-btn {
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
}
|
187
doc/index.rst
187
doc/index.rst
@ -1,47 +1,48 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
C++ Format (cppformat) is an open-source formatting library for C++.
|
||||
It can be used as a safe alternative to printf or as a fast
|
||||
alternative to IOStreams.
|
||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">What users say:</div>
|
||||
<div class="panel-body">
|
||||
Thanks for creating this library. It’s been a hole in C++ for a long time.
|
||||
I’ve used both boost::format and loki::SPrintf, and neither felt like the
|
||||
right answer. This does.
|
||||
Thanks for creating this library. It’s been a hole in C++ for
|
||||
a long time. I’ve used both <code>boost::format</code> and
|
||||
<code>loki::SPrintf</code>, and neither felt like the right answer.
|
||||
This does.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
.. _format-api:
|
||||
.. _format-api-intro:
|
||||
|
||||
Format API
|
||||
----------
|
||||
|
||||
The replacement-based Format API provides a safe alternative to ``printf``,
|
||||
``sprintf`` and friends with comparable or `better performance
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
The `format string syntax <doc/latest/index.html#format-string-syntax>`_ is similar
|
||||
to the one used by `str.format <http://docs.python.org/2/library/stdtypes.html#str.format>`_
|
||||
in Python:
|
||||
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>`_
|
||||
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
|
||||
Python:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::format("The answer is {}", 42);
|
||||
fmt::format("The answer is {}.", 42);
|
||||
|
||||
The ``fmt::format`` function returns a string "The answer is 42". You can use
|
||||
``fmt::MemoryWriter`` to avoid constructing ``std::string``:
|
||||
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::MemoryWriter w;
|
||||
w.write("Look, a {} string", 'C');
|
||||
w.c_str(); // returns a C string (const char*)
|
||||
fmt::memory_buffer out;
|
||||
format_to(out, "For a moment, {} happened.", "nothing");
|
||||
out.data(); // returns a pointer to the formatted data
|
||||
|
||||
The ``fmt::print`` function performs formatting and writes the result to a file:
|
||||
The ``fmt::print`` function performs formatting and writes the result to a stream:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -54,11 +55,6 @@ The file argument can be omitted in which case the function prints to
|
||||
|
||||
fmt::print("Don't {}\n", "panic");
|
||||
|
||||
If your compiler supports C++11, then the formatting functions are implemented
|
||||
with variadic templates. Otherwise variadic functions are emulated by generating
|
||||
a set of lightweight wrappers. This ensures compatibility with older compilers
|
||||
while providing a natural API.
|
||||
|
||||
The Format API also supports positional arguments useful for localization:
|
||||
|
||||
.. code:: c++
|
||||
@ -66,7 +62,7 @@ The Format API also supports positional arguments useful for localization:
|
||||
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 values are being inserted:
|
||||
what goes where when multiple arguments are being formatted:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -78,53 +74,35 @@ an alternative, slightly terser syntax for named arguments:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
using namespace fmt::literals;
|
||||
fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.",
|
||||
"name"_a="World", "number"_a=42);
|
||||
|
||||
The ``_format`` suffix may be used to format string literals similar to Python:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string message = "{0}{1}{0}"_format("abra", "cad");
|
||||
|
||||
Other than the placement of the format string on the left of the operator,
|
||||
``_format`` is functionally identical to ``fmt::format``. In order to use the
|
||||
literal operators, they must be made visible with the directive
|
||||
``using namespace fmt::literals;``. Note that this brings in only ``_a`` and
|
||||
``_format`` but nothing else from the ``fmt`` namespace.
|
||||
|
||||
.. _write-api:
|
||||
|
||||
Write API
|
||||
---------
|
||||
|
||||
The concatenation-based Write API (experimental) provides a
|
||||
`fast <http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
|
||||
stateless alternative to IOStreams:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::MemoryWriter out;
|
||||
out << "The answer in hexadecimal is " << hex(42);
|
||||
|
||||
.. _safety:
|
||||
|
||||
Safety
|
||||
------
|
||||
|
||||
The library is fully type safe, automatic memory management prevents buffer overflow,
|
||||
errors in format strings are reported using exceptions. For example, the code
|
||||
The library is fully type safe, automatic memory management prevents buffer
|
||||
overflow, errors in format strings are reported using exceptions or at compile
|
||||
time. For example, the code
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::format("The answer is {:d}", "forty-two");
|
||||
|
||||
throws a ``FormatError`` 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.
|
||||
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
|
||||
|
||||
Where possible, errors are caught at compile time. For example, 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.
|
||||
|
||||
The following code
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -138,44 +116,65 @@ formatted into a narrow string. You can use a wide format string instead:
|
||||
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.
|
||||
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.
|
||||
|
||||
Compact Binary Code
|
||||
-------------------
|
||||
|
||||
The library is designed to produce compact per-call compiled code. For example
|
||||
(`godbolt <https://godbolt.org/g/TZU4KF>`_),
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main() {
|
||||
fmt::print("The answer is {}.", 42);
|
||||
}
|
||||
|
||||
compiles to just
|
||||
|
||||
.. code:: asm
|
||||
|
||||
main: # @main
|
||||
sub rsp, 24
|
||||
mov qword ptr [rsp], 42
|
||||
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)
|
||||
xor eax, eax
|
||||
add rsp, 24
|
||||
ret
|
||||
.L.str:
|
||||
.asciz "The answer is {}."
|
||||
|
||||
.. _portability:
|
||||
|
||||
Portability
|
||||
-----------
|
||||
|
||||
C++ Format is highly portable. Here is an incomplete list of operating systems and
|
||||
compilers where it has been tested and known to work:
|
||||
The library is highly portable and relies only on a small set of C++11 features:
|
||||
|
||||
* 64-bit (amd64) GNU/Linux with GCC 4.4.3, `4.6.3 <https://travis-ci.org/cppformat/cppformat>`_,
|
||||
4.7.2, 4.8.1 and Intel C++ Compiler (ICC) 14.0.2
|
||||
* variadic templates
|
||||
* type traits
|
||||
* rvalue references
|
||||
* decltype
|
||||
* trailing return types
|
||||
* deleted functions
|
||||
* alias templates
|
||||
|
||||
* 32-bit (i386) GNU/Linux with GCC 4.4.3, 4.6.3
|
||||
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.
|
||||
|
||||
* Mac OS X with GCC 4.2.1 and Clang 4.2, 5.1.0
|
||||
|
||||
* 64-bit Windows with Visual C++ 2010, 2013 and
|
||||
`2015 <https://ci.appveyor.com/project/vitaut/cppformat>`_
|
||||
|
||||
* 32-bit Windows with Visual C++ 2010
|
||||
|
||||
Although the library uses C++11 features when available, it also works with older
|
||||
compilers and standard library implementations. The only thing to keep in mind
|
||||
for C++98 portability:
|
||||
|
||||
* Variadic templates: minimum GCC 4.4, Clang 2.9 or VS2013. This feature allows
|
||||
the Format API to accept an unlimited number of arguments. With older compilers
|
||||
the maximum is 15.
|
||||
|
||||
* User-defined literals: minimum GCC 4.7, Clang 3.1 or VS2015. The suffixes
|
||||
``_format`` and ``_a`` are functionally equivalent to the functions
|
||||
``fmt::format`` and ``fmt::arg``.
|
||||
|
||||
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 in this case. For example,
|
||||
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,
|
||||
|
||||
.. code::
|
||||
|
||||
@ -188,16 +187,16 @@ always prints ``inf``.
|
||||
Ease of Use
|
||||
-----------
|
||||
|
||||
C++ Format has small self-contained code base consisting of a single header file
|
||||
and a single source file and no external dependencies. A permissive BSD `license
|
||||
<https://github.com/cppformat/cppformat#license>`_ allows using the library both
|
||||
in open-source and commercial projects.
|
||||
{fmt} has a small self-contained code base with the core library consisting of
|
||||
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.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<a class="btn btn-success" href="https://github.com/cppformat/cppformat">GitHub Repository</a>
|
||||
<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=cppformat&repo=cppformat&type=watch&count=true"
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=fmtlib&repo=fmt&type=watch&count=true"
|
||||
class="github-btn" width="100" height="20"></iframe>
|
||||
</div>
|
||||
|
164
doc/syntax.rst
164
doc/syntax.rst
@ -4,8 +4,9 @@
|
||||
Format String Syntax
|
||||
********************
|
||||
|
||||
Formatting functions such as :ref:`fmt::format() <format>` and :ref:`fmt::print() <print>`
|
||||
use the same format string syntax described in this section.
|
||||
Formatting functions such as :ref:`fmt::format() <format>` and
|
||||
:ref:`fmt::print() <print>` use the same format string syntax described in this
|
||||
section.
|
||||
|
||||
Format strings contain "replacement fields" surrounded by curly braces ``{}``.
|
||||
Anything that is not contained in braces is considered literal text, which is
|
||||
@ -35,6 +36,8 @@ If the numerical arg_ids in a format string are 0, 1, 2, ... in sequence,
|
||||
they can all be omitted (not just some) and the numbers 0, 1, 2, ... will be
|
||||
automatically inserted in that order.
|
||||
|
||||
Named arguments can be referred to by their names or indices.
|
||||
|
||||
Some simple format string examples::
|
||||
|
||||
"First, thou shalt count to {0}" // References the first argument
|
||||
@ -49,12 +52,10 @@ mini-language" or interpretation of the *format_spec*.
|
||||
Most built-in types support a common formatting mini-language, which is
|
||||
described in the next section.
|
||||
|
||||
A *format_spec* field can also include nested replacement fields within it.
|
||||
These nested replacement fields can contain only an argument index;
|
||||
format specifications are not allowed. Formatting is performed as if the
|
||||
replacement fields within the format_spec are substituted before the
|
||||
*format_spec* string is interpreted. This allows the formatting of a value
|
||||
to be dynamically specified.
|
||||
A *format_spec* field can also include nested replacement fields in certain
|
||||
positions within it. These nested replacement fields can contain only an
|
||||
argument id; format specifications are not allowed. This allows the formatting
|
||||
of a value to be dynamically specified.
|
||||
|
||||
See the :ref:`formatexamples` section for some examples.
|
||||
|
||||
@ -76,18 +77,18 @@ The general form of a *standard format specifier* is:
|
||||
.. productionlist:: sf
|
||||
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`]
|
||||
fill: <a character other than '{' or '}'>
|
||||
align: "<" | ">" | "=" | "^"
|
||||
align: "<" | ">" | "^"
|
||||
sign: "+" | "-" | " "
|
||||
width: `integer` | "{" `arg_id` "}"
|
||||
precision: `integer` | "{" `arg_id` "}"
|
||||
type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
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"
|
||||
|
||||
The *fill* character can be any character other than '{' or '}'. The presence
|
||||
of a fill character is signaled by the character following it, which must be
|
||||
one of the alignment options. If the second character of *format_spec* is not
|
||||
a valid alignment option, then it is assumed that both the fill character and
|
||||
the alignment option are absent.
|
||||
The *fill* character can be any Unicode code point other than ``'{'`` or
|
||||
``'}'``. The presence of a fill character is signaled by the character following
|
||||
it, which must be one of the alignment options. If the second character of
|
||||
*format_spec* is not a valid alignment option, then it is assumed that both the
|
||||
fill character and the alignment option are absent.
|
||||
|
||||
The meaning of the various alignment options is as follows:
|
||||
|
||||
@ -100,11 +101,6 @@ The meaning of the various alignment options is as follows:
|
||||
| ``'>'`` | Forces the field to be right-aligned within the |
|
||||
| | available space (this is the default for numbers). |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'='`` | Forces the padding to be placed after the sign (if any) |
|
||||
| | but before the digits. This is used for printing fields |
|
||||
| | in the form '+000000120'. This alignment option is only |
|
||||
| | valid for numeric types. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'^'`` | Forces the field to be centered within the available |
|
||||
| | space. |
|
||||
+---------+----------------------------------------------------------+
|
||||
@ -147,15 +143,17 @@ conversions, trailing zeros are not removed from the result.
|
||||
.. ifconfig:: False
|
||||
|
||||
The ``','`` option signals the use of a comma for a thousands separator.
|
||||
For a locale aware separator, use the ``'n'`` integer presentation type
|
||||
For a locale aware separator, use the ``'L'`` integer presentation type
|
||||
instead.
|
||||
|
||||
*width* is a decimal integer defining the minimum field width. If not
|
||||
specified, then the field width will be determined by the content.
|
||||
|
||||
Preceding the *width* field by a zero (``'0'``) character enables
|
||||
sign-aware zero-padding for numeric types. This is equivalent to a *fill*
|
||||
character of ``'0'`` with an *alignment* type of ``'='``.
|
||||
Preceding the *width* field by a zero (``'0'``) character enables sign-aware
|
||||
zero-padding for numeric types. It forces the padding to be placed after the
|
||||
sign or base (if any) but before the digits. This is used for printing fields in
|
||||
the form '+000000120'. This option is only valid for numeric types and it has no
|
||||
effect on formatting of infinity and NaN.
|
||||
|
||||
The *precision* is a decimal number indicating how many digits should be
|
||||
displayed after the decimal point for a floating-point value formatted with
|
||||
@ -216,6 +214,10 @@ 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'``. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
@ -230,7 +232,7 @@ The available presentation types for floating-point values are:
|
||||
+=========+==========================================================+
|
||||
| ``'a'`` | Hexadecimal floating point format. Prints the number in |
|
||||
| | base 16 with prefix ``"0x"`` and lower-case letters for |
|
||||
| | digits above 9. Uses 'p' to indicate the exponent. |
|
||||
| | digits above 9. Uses ``'p'`` to indicate the exponent. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'A'`` | Same as ``'a'`` except it uses upper-case letters for |
|
||||
| | the prefix, digits above 9 and to indicate the exponent. |
|
||||
@ -239,7 +241,7 @@ The available presentation types for floating-point values are:
|
||||
| | notation using the letter 'e' to indicate the exponent. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'E'`` | Exponent notation. Same as ``'e'`` except it uses an |
|
||||
| | upper-case 'E' as the separator character. |
|
||||
| | upper-case ``'E'`` as the separator character. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'f'`` | Fixed point. Displays the number as a fixed-point |
|
||||
| | number. |
|
||||
@ -259,7 +261,14 @@ 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. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| none | The same as ``'g'``. |
|
||||
| ``'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. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
.. ifconfig:: False
|
||||
@ -296,7 +305,7 @@ The available presentation types for pointers are:
|
||||
|
||||
.. _formatexamples:
|
||||
|
||||
Format examples
|
||||
Format Examples
|
||||
===============
|
||||
|
||||
This section contains examples of the format syntax and comparison with
|
||||
@ -311,64 +320,94 @@ following examples.
|
||||
|
||||
Accessing arguments by position::
|
||||
|
||||
format("{0}, {1}, {2}", 'a', 'b', 'c');
|
||||
fmt::format("{0}, {1}, {2}", 'a', 'b', 'c');
|
||||
// Result: "a, b, c"
|
||||
format("{}, {}, {}", 'a', 'b', 'c');
|
||||
fmt::format("{}, {}, {}", 'a', 'b', 'c');
|
||||
// Result: "a, b, c"
|
||||
format("{2}, {1}, {0}", 'a', 'b', 'c');
|
||||
fmt::format("{2}, {1}, {0}", 'a', 'b', 'c');
|
||||
// Result: "c, b, a"
|
||||
format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated
|
||||
fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated
|
||||
// Result: "abracadabra"
|
||||
|
||||
Aligning the text and specifying a width::
|
||||
|
||||
format("{:<30}", "left aligned");
|
||||
fmt::format("{:<30}", "left aligned");
|
||||
// Result: "left aligned "
|
||||
format("{:>30}", "right aligned");
|
||||
fmt::format("{:>30}", "right aligned");
|
||||
// Result: " right aligned"
|
||||
format("{:^30}", "centered");
|
||||
fmt::format("{:^30}", "centered");
|
||||
// Result: " centered "
|
||||
format("{:*^30}", "centered"); // use '*' as a fill char
|
||||
fmt::format("{:*^30}", "centered"); // use '*' as a fill char
|
||||
// Result: "***********centered***********"
|
||||
|
||||
Dynamic width::
|
||||
|
||||
fmt::format("{:<{}}", "left aligned", 30);
|
||||
// Result: "left aligned "
|
||||
|
||||
Dynamic precision::
|
||||
|
||||
fmt::format("{:.{}f}", 3.14, 1);
|
||||
// Result: "3.1"
|
||||
|
||||
Replacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign::
|
||||
|
||||
format("{:+f}; {:+f}", 3.14, -3.14); // show it always
|
||||
fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always
|
||||
// Result: "+3.140000; -3.140000"
|
||||
format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers
|
||||
fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers
|
||||
// Result: " 3.140000; -3.140000"
|
||||
format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}'
|
||||
fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}'
|
||||
// Result: "3.140000; -3.140000"
|
||||
|
||||
Replacing ``%x`` and ``%o`` and converting the value to different bases::
|
||||
|
||||
format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
// Result: "int: 42; hex: 2a; oct: 52; bin: 101010"
|
||||
// with 0x or 0 or 0b as prefix:
|
||||
format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42);
|
||||
fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42);
|
||||
// Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010"
|
||||
|
||||
Padded hex byte with prefix and always prints both hex characters::
|
||||
|
||||
fmt::format("{:#04x}", 0);
|
||||
// Result: "0x00"
|
||||
|
||||
Box drawing using Unicode fill::
|
||||
|
||||
fmt::print(
|
||||
"┌{0:─^{2}}┐\n"
|
||||
"│{1: ^{2}}│\n"
|
||||
"└{0:─^{2}}┘\n", "", "Hello, world!", 20);
|
||||
|
||||
prints::
|
||||
|
||||
┌────────────────────┐
|
||||
│ Hello, world! │
|
||||
└────────────────────┘
|
||||
|
||||
Using type-specific formatting::
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
auto t = tm();
|
||||
t.tm_year = 2010 - 1900;
|
||||
t.tm_mon = 6;
|
||||
t.tm_mday = 4;
|
||||
t.tm_hour = 12;
|
||||
t.tm_min = 15;
|
||||
t.tm_sec = 58;
|
||||
fmt::print("{:%Y-%m-%d %H:%M:%S}", t);
|
||||
// Prints: 2010-08-04 12:15:58
|
||||
|
||||
Using the comma as a thousands separator::
|
||||
|
||||
#include <fmt/locale.h>
|
||||
|
||||
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
|
||||
// s == "1,234,567,890"
|
||||
|
||||
.. ifconfig:: False
|
||||
|
||||
Using the comma as a thousands separator::
|
||||
|
||||
format("{:,}", 1234567890);
|
||||
'1,234,567,890'
|
||||
|
||||
Expressing a percentage::
|
||||
|
||||
>>> points = 19
|
||||
>>> total = 22
|
||||
Format("Correct answers: {:.2%}") << points/total)
|
||||
'Correct answers: 86.36%'
|
||||
|
||||
Using type-specific formatting::
|
||||
|
||||
>>> import datetime
|
||||
>>> d = datetime.datetime(2010, 7, 4, 12, 15, 58)
|
||||
Format("{:%Y-%m-%d %H:%M:%S}") << d)
|
||||
'2010-07-04 12:15:58'
|
||||
|
||||
Nesting arguments and more complex examples::
|
||||
|
||||
>>> for align, text in zip('<^>', ['left', 'center', 'right']):
|
||||
@ -397,4 +436,3 @@ Replacing ``%x`` and ``%o`` and converting the value to different bases::
|
||||
9 9 11 1001
|
||||
10 A 12 1010
|
||||
11 B 13 1011
|
||||
|
||||
|
115
doc/usage.rst
115
doc/usage.rst
@ -2,46 +2,44 @@
|
||||
Usage
|
||||
*****
|
||||
|
||||
To use the C++ Format library, add :file:`format.h` and :file:`format.cc` from
|
||||
a `release archive <https://github.com/cppformat/cppformat/releases/latest>`_
|
||||
or the `Git repository <https://github.com/cppformat/cppformat>`_ to your project.
|
||||
To use the {fmt} library, add :file:`fmt/core.h`, :file:`fmt/format.h`,
|
||||
:file:`fmt/format-inl.h`, :file:`src/format.cc` and optionally other headers
|
||||
from a `release archive <https://github.com/fmtlib/fmt/releases/latest>`_ or
|
||||
the `Git repository <https://github.com/fmtlib/fmt>`_ to your project.
|
||||
Alternatively, you can :ref:`build the library with CMake <building>`.
|
||||
|
||||
If you are using Visual C++ with precompiled headers, you might need to add
|
||||
the line ::
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
before other includes in :file:`format.cc`.
|
||||
|
||||
.. _building:
|
||||
|
||||
Building the library
|
||||
Building the Library
|
||||
====================
|
||||
|
||||
The included `CMake build script`__ can be used to build the C++ Format
|
||||
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/.
|
||||
|
||||
__ https://github.com/cppformat/cppformat/blob/master/CMakeLists.txt
|
||||
__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt
|
||||
|
||||
CMake works by generating native makefiles or project files that can
|
||||
be used in the compiler environment of your choice. The typical
|
||||
workflow starts with::
|
||||
|
||||
mkdir build # Create a directory to hold the build output.
|
||||
mkdir build # Create a directory to hold the build output.
|
||||
cd build
|
||||
cmake <path/to/cppformat> # Generate native build scripts.
|
||||
cmake .. # Generate native build scripts.
|
||||
|
||||
where :file:`{<path/to/cppformat>}` is a path to the ``cppformat`` repository.
|
||||
where :file:`{<path/to/fmt>}` is a path to the ``fmt`` repository.
|
||||
|
||||
If you are on a \*nix system, you should now see a Makefile in the
|
||||
current directory. Now you can build C++ Format by running :command:`make`.
|
||||
current directory. Now you can build the library by running :command:`make`.
|
||||
|
||||
Once the library has been built you can invoke :command:`make test` to run
|
||||
the tests.
|
||||
|
||||
If you use Windows and have Visual Studio installed, a :file:`FORMAT.sln`
|
||||
You can control generation of the make ``test`` target with the ``FMT_TEST``
|
||||
CMake option. This can be useful if you include fmt as a subdirectory in
|
||||
your project but don't want to add fmt's tests to your ``test`` target.
|
||||
|
||||
If you use Windows and have Visual Studio installed, a :file:`FMT.sln`
|
||||
file and several :file:`.vcproj` files will be created. You can then build them
|
||||
using Visual Studio or msbuild.
|
||||
|
||||
@ -54,7 +52,38 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
|
||||
|
||||
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
||||
|
||||
Building the documentation
|
||||
Installing the Library
|
||||
======================
|
||||
|
||||
After building the library you can install it on a Unix-like system by running
|
||||
:command:`sudo make install`.
|
||||
|
||||
Usage with CMake
|
||||
================
|
||||
|
||||
You can add the ``fmt`` library directory into your project and include it in
|
||||
your ``CMakeLists.txt`` file::
|
||||
|
||||
add_subdirectory(fmt)
|
||||
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||
|
||||
to exclude it from ``make``, ``make all``, or ``cmake --build .``.
|
||||
|
||||
You can detect and use an installed version of {fmt} as follows::
|
||||
|
||||
find_package(fmt)
|
||||
target_link_libraries(<your-target> fmt::fmt)
|
||||
|
||||
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)
|
||||
|
||||
Building the Documentation
|
||||
==========================
|
||||
|
||||
To build the documentation you need the following software installed on your
|
||||
@ -62,29 +91,59 @@ system:
|
||||
|
||||
* `Python <https://www.python.org/>`_ with pip and virtualenv
|
||||
* `Doxygen <http://www.stack.nl/~dimitri/doxygen/>`_
|
||||
* `Less <http://lesscss.org/>`_ with less-plugin-clean-css
|
||||
* `Less <http://lesscss.org/>`_ with ``less-plugin-clean-css``.
|
||||
Ubuntu doesn't package the ``clean-css`` plugin so you should use ``npm``
|
||||
instead of ``apt`` to install both ``less`` and the plugin::
|
||||
|
||||
sudo npm install -g less less-plugin-clean-css.
|
||||
|
||||
First generate makefiles or project files using CMake as described in
|
||||
the previous section. Then compile the ``doc`` target/project, for example::
|
||||
|
||||
make doc
|
||||
|
||||
This will generate the HTML documenation in ``doc/html``.
|
||||
|
||||
This will generate the HTML documentation in ``doc/html``.
|
||||
|
||||
Conda
|
||||
=====
|
||||
|
||||
fmt can be installed on Linux, macOS and Windows with
|
||||
`Conda <https://docs.conda.io/en/latest/>`__, using its
|
||||
`conda-forge <https://conda-forge.org>`__
|
||||
`package <https://github.com/conda-forge/fmt-feedstock>`__, as follows::
|
||||
|
||||
conda install -c conda-forge fmt
|
||||
|
||||
Vcpkg
|
||||
=====
|
||||
|
||||
You can download and install fmt using the `vcpkg
|
||||
<https://github.com/Microsoft/vcpkg>`__ dependency manager::
|
||||
|
||||
git clone https://github.com/Microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
./bootstrap-vcpkg.sh
|
||||
./vcpkg integrate install
|
||||
./vcpkg install fmt
|
||||
|
||||
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.
|
||||
|
||||
Android NDK
|
||||
===========
|
||||
|
||||
C++ Format provides `Android.mk file`__ that can be used to build the library
|
||||
fmt provides `Android.mk file`__ that can be used to build the library
|
||||
with `Android NDK <https://developer.android.com/tools/sdk/ndk/index.html>`_.
|
||||
For an example of using C++ Format with Android NDK, see the
|
||||
`android-ndk-example <https://github.com/cppformat/android-ndk-example>`_
|
||||
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/cppformat/cppformat/blob/master/Android.mk
|
||||
__ https://github.com/fmtlib/fmt/blob/master/Android.mk
|
||||
|
||||
Homebrew
|
||||
========
|
||||
|
||||
C++ Format can be installed on OS X using `Homebrew <http://brew.sh/>`_::
|
||||
fmt can be installed on OS X using `Homebrew <http://brew.sh/>`_::
|
||||
|
||||
brew install cppformat
|
||||
brew install fmt
|
||||
|
1123
include/fmt/chrono.h
Normal file
1123
include/fmt/chrono.h
Normal file
File diff suppressed because it is too large
Load Diff
566
include/fmt/color.h
Normal file
566
include/fmt/color.h
Normal file
@ -0,0 +1,566 @@
|
||||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
enum class terminal_color : uint8_t {
|
||||
black = 30,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black = 90,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white
|
||||
};
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
|
||||
value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
|
||||
value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// Experimental text formatting support.
|
||||
class text_style {
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems(em) {}
|
||||
|
||||
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 OR a terminal color"));
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
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;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs &= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||
return set_foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
||||
return set_background_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
detail::color_type text_color) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/true, foreground);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
const char* esc) FMT_NOEXCEPT {
|
||||
// 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;
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
||||
if (value >= 100u) {
|
||||
buffer[index++] = static_cast<Char>('1');
|
||||
value %= 100u;
|
||||
}
|
||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
||||
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[index++] = static_cast<Char>('\0');
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
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;
|
||||
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
}
|
||||
buffer[index++] = static_cast<Char>(0);
|
||||
}
|
||||
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 {
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
out[0] = static_cast<Char>('0' + c / 100);
|
||||
out[1] = static_cast<Char>('0' + c / 10 % 10);
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputs(chars, stream);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputws(chars, stream);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(detail::data::reset_color, stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(detail::data::wreset_color, 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);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
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_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
detail::fputs(buf.data(), f);
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify text formatting.
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include <fmt/color.h>
|
||||
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"The answer is {}", 42);
|
||||
\endrst
|
||||
*/
|
||||
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...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
665
include/fmt/compile.h
Normal file
665
include/fmt/compile.h
Normal file
@ -0,0 +1,665 @@
|
||||
// Formatting library for C++ - experimental format string compilation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
|
||||
template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Converts a string literal *s* into a format string that will be parsed at
|
||||
compile time and converted into efficient formatting code. Requires C++17
|
||||
``constexpr if`` compiler support.
|
||||
|
||||
**Example**::
|
||||
|
||||
// Converts 42 into std::string using the most efficient method and no
|
||||
// runtime format string processing.
|
||||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
const T& first(const T& value, const Tail&...) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Part of a compiled format string. It can be either literal text or a
|
||||
// replacement field.
|
||||
template <typename Char> struct format_part {
|
||||
enum class kind { arg_index, arg_name, text, replacement };
|
||||
|
||||
struct replacement {
|
||||
arg_ref<Char> arg_id;
|
||||
dynamic_format_specs<Char> specs;
|
||||
};
|
||||
|
||||
kind part_kind;
|
||||
union value {
|
||||
int arg_index;
|
||||
basic_string_view<Char> str;
|
||||
replacement repl;
|
||||
|
||||
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
|
||||
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
|
||||
FMT_CONSTEXPR value(replacement r) : repl(r) {}
|
||||
} val;
|
||||
// Position past the end of the argument id.
|
||||
const Char* arg_id_end = nullptr;
|
||||
|
||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
||||
: part_kind(k), val(v) {}
|
||||
|
||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
||||
return format_part(kind::arg_index, index);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
||||
return format_part(kind::arg_name, name);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
||||
return format_part(kind::text, text);
|
||||
}
|
||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
||||
return format_part(kind::replacement, repl);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct part_counter {
|
||||
unsigned num_parts = 0;
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end) ++num_parts;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
|
||||
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
|
||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
|
||||
return ++num_parts, 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned brace_counter = 0;
|
||||
for (; begin != end; ++begin) {
|
||||
if (*begin == '{') {
|
||||
++brace_counter;
|
||||
} else if (*begin == '}') {
|
||||
if (brace_counter == 0u) break;
|
||||
--brace_counter;
|
||||
}
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char*) {}
|
||||
};
|
||||
|
||||
// Counts the number of parts in a format string.
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
||||
part_counter<Char> counter;
|
||||
parse_format_string<true>(format_str, counter);
|
||||
return counter.num_parts;
|
||||
}
|
||||
|
||||
template <typename Char, typename PartHandler>
|
||||
class format_string_compiler : public error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
|
||||
PartHandler handler_;
|
||||
part part_;
|
||||
basic_string_view<Char> format_str_;
|
||||
basic_format_parse_context<Char> parse_context_;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
||||
PartHandler handler)
|
||||
: handler_(handler),
|
||||
format_str_(format_str),
|
||||
parse_context_(format_str) {}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end)
|
||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id() {
|
||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id(int id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
part_ = part::make_arg_index(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
|
||||
part_ = part::make_arg_name(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
|
||||
part_.arg_id_end = ptr;
|
||||
handler_(part_);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
||||
const Char* end) {
|
||||
auto repl = typename part::replacement();
|
||||
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
|
||||
repl.specs, parse_context_);
|
||||
auto it = parse_format_specs(begin, end, handler);
|
||||
if (*it != '}') on_error("missing '}' in format string");
|
||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
||||
? arg_ref<Char>(part_.val.arg_index)
|
||||
: arg_ref<Char>(part_.val.str);
|
||||
auto part = part::make_replacement(repl);
|
||||
part.arg_id_end = begin;
|
||||
handler_(part);
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
// Compiles a format string and invokes handler(part) for each parsed part.
|
||||
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
||||
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
||||
PartHandler handler) {
|
||||
parse_format_string<IS_CONSTEXPR>(
|
||||
format_str,
|
||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Context, typename Id>
|
||||
void format_arg(
|
||||
basic_format_parse_context<typename Context::char_type>& parse_ctx,
|
||||
Context& ctx, Id arg_id) {
|
||||
ctx.advance_to(visit_format_arg(
|
||||
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
|
||||
ctx.arg(arg_id)));
|
||||
}
|
||||
|
||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
||||
namespace cf {
|
||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
||||
auto vformat_to(OutputIt out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) -> typename Context::iterator {
|
||||
using char_type = typename Context::char_type;
|
||||
basic_format_parse_context<char_type> parse_ctx(
|
||||
to_string_view(cf.format_str_));
|
||||
Context ctx(out, args);
|
||||
|
||||
const auto& parts = cf.parts();
|
||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
||||
++part_it) {
|
||||
const auto& part = *part_it;
|
||||
const auto& value = part.val;
|
||||
|
||||
using format_part_t = format_part<char_type>;
|
||||
switch (part.part_kind) {
|
||||
case format_part_t::kind::text: {
|
||||
const auto text = value.str;
|
||||
auto output = ctx.out();
|
||||
auto&& it = reserve(output, text.size());
|
||||
it = std::copy_n(text.begin(), text.size(), it);
|
||||
ctx.advance_to(output);
|
||||
break;
|
||||
}
|
||||
|
||||
case format_part_t::kind::arg_index:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::arg_name:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::replacement: {
|
||||
const auto& arg_id_value = value.repl.arg_id.val;
|
||||
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
|
||||
? ctx.arg(arg_id_value.index)
|
||||
: ctx.arg(arg_id_value.name);
|
||||
|
||||
auto specs = value.repl.specs;
|
||||
|
||||
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
|
||||
handle_dynamic_spec<precision_checker>(specs.precision,
|
||||
specs.precision_ref, ctx);
|
||||
|
||||
error_handler h;
|
||||
numeric_specs_checker<error_handler> checker(h, arg.type());
|
||||
if (specs.align == align::numeric) checker.require_numeric_argument();
|
||||
if (specs.sign != sign::none) checker.check_sign();
|
||||
if (specs.alt) checker.require_numeric_argument();
|
||||
if (specs.precision >= 0) checker.check_precision();
|
||||
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
|
||||
ctx, nullptr, &specs),
|
||||
arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
} // namespace cf
|
||||
|
||||
struct basic_compiled_format {};
|
||||
|
||||
template <typename S, typename = void>
|
||||
struct compiled_format_base : basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
using parts_container = std::vector<detail::format_part<char_type>>;
|
||||
|
||||
parts_container compiled_parts;
|
||||
|
||||
explicit compiled_format_base(basic_string_view<char_type> format_str) {
|
||||
compile_format_string<false>(format_str,
|
||||
[this](const format_part<char_type>& part) {
|
||||
compiled_parts.push_back(part);
|
||||
});
|
||||
}
|
||||
|
||||
const parts_container& parts() const { return compiled_parts; }
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N> struct format_part_array {
|
||||
format_part<Char> data[N] = {};
|
||||
FMT_CONSTEXPR format_part_array() = default;
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N>
|
||||
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
|
||||
basic_string_view<Char> format_str) {
|
||||
format_part_array<Char, N> parts;
|
||||
unsigned counter = 0;
|
||||
// This is not a lambda for compatibility with older compilers.
|
||||
struct {
|
||||
format_part<Char>* parts;
|
||||
unsigned* counter;
|
||||
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
||||
parts[(*counter)++] = part;
|
||||
}
|
||||
} collector{parts.data, &counter};
|
||||
compile_format_string<true>(format_str, collector);
|
||||
if (counter < N) {
|
||||
parts.data[counter] =
|
||||
format_part<Char>::make_text(basic_string_view<Char>());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
||||
: basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
|
||||
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
|
||||
|
||||
// Workaround for old compilers. Format string compilation will not be
|
||||
// performed there anyway.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
||||
#else
|
||||
static const unsigned num_format_parts = 1;
|
||||
#endif
|
||||
|
||||
using parts_container = format_part<char_type>[num_format_parts];
|
||||
|
||||
const parts_container& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
||||
compile_to_parts<char_type, num_format_parts>(
|
||||
detail::to_string_view(S()));
|
||||
return compiled_parts.data;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename... Args>
|
||||
class compiled_format : private compiled_format_base<S> {
|
||||
public:
|
||||
using typename compiled_format_base<S>::char_type;
|
||||
|
||||
private:
|
||||
basic_string_view<char_type> format_str_;
|
||||
|
||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) ->
|
||||
typename Context::iterator;
|
||||
|
||||
public:
|
||||
compiled_format() = delete;
|
||||
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
|
||||
: compiled_format_base<S>(format_str), format_str_(format_str) {}
|
||||
};
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename T> struct is_compiled_format : std::false_type {};
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, data);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N and has format specifiers.
|
||||
template <typename Char, typename T, int N> struct spec_field {
|
||||
using char_type = Char;
|
||||
mutable formatter<T, Char> fmt;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, {});
|
||||
return fmt.format(arg, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS !=
|
||||
basic_string_view<typename S::char_type>(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Char> struct parse_specs_result {
|
||||
formatter<T, Char> fmt;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = basic_format_parse_context<Char>(str);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + (end - str.data()) + 1};
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr basic_string_view<char_type> str = format_str;
|
||||
if constexpr (str[POS] == '{') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}') {
|
||||
using type = get_type<ID, Args>;
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else if constexpr (str[POS + 1] == ':') {
|
||||
using type = get_type<ID, Args>;
|
||||
constexpr auto result = parse_specs<type>(str, POS + 2);
|
||||
return parse_tail<Args, result.end, ID + 1>(
|
||||
spec_field<char_type, type, ID>{result.fmt}, format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value ||
|
||||
detail::is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
||||
if constexpr (str.size() == 0) {
|
||||
return detail::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
||||
detail::unknown_format>()) {
|
||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
|
||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
|
||||
// Compiles the format string which must be a string literal.
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N])
|
||||
-> detail::compiled_format<const Char*, Args...> {
|
||||
return detail::compiled_format<const Char*, Args...>(
|
||||
basic_string_view<Char>(format_str, N - 1));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// DEPRECATED! use FMT_COMPILE instead.
|
||||
template <typename... Args>
|
||||
FMT_DEPRECATED auto compile(const Args&... args)
|
||||
-> decltype(detail::compile(args...)) {
|
||||
return detail::compile(args...);
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_constexpr
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::buffer<Char>& base = buffer;
|
||||
cf.format(std::back_inserter(base), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using context = buffer_context<Char>;
|
||||
detail::buffer<Char>& base = buffer;
|
||||
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
|
||||
make_format_args<context>(args...));
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
constexpr basic_string_view<typename S::char_type> str = S();
|
||||
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
|
||||
return fmt::to_string(detail::first(args...));
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format(compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
using char_type = typename CompiledFormat::char_type;
|
||||
using context = format_context_t<OutputIt, char_type>;
|
||||
return detail::cf::vformat_to<context>(out, cf,
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format_to(out, compiled, args...);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
|
||||
detail::basic_compiled_format, CompiledFormat>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto it =
|
||||
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return format_to(detail::counting_iterator(), cf, args...).count();
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
1878
include/fmt/core.h
Normal file
1878
include/fmt/core.h
Normal file
File diff suppressed because it is too large
Load Diff
1453
include/fmt/format-inl.h
Normal file
1453
include/fmt/format-inl.h
Normal file
File diff suppressed because it is too large
Load Diff
3717
include/fmt/format.h
Normal file
3717
include/fmt/format.h
Normal file
File diff suppressed because it is too large
Load Diff
78
include/fmt/locale.h
Normal file
78
include/fmt/locale.h
Normal file
@ -0,0 +1,78 @@
|
||||
// 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_
|
450
include/fmt/os.h
Normal file
450
include/fmt/os.h
Normal file
@ -0,0 +1,450 @@
|
||||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#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 <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // for strtod_l
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// 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
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
(result) = (expression); \
|
||||
} while ((result) == (error_result) && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
private:
|
||||
int value_;
|
||||
|
||||
public:
|
||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace detail {
|
||||
// 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 {
|
||||
private:
|
||||
memory_buffer buffer_;
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(wstring_view 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]; }
|
||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||
|
||||
// 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 void format_windows_error(buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
} // namespace detail
|
||||
|
||||
/** A Windows error. */
|
||||
class windows_error : public system_error {
|
||||
private:
|
||||
FMT_API void init(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
|
||||
|
||||
.. 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".
|
||||
|
||||
**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...));
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
#endif // _WIN32
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
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.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
file& operator=(file&& other) FMT_NOEXCEPT {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API size_t read(void* buffer, size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API size_t write(const void* buffer, size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// 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;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
class direct_buffered_file;
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args);
|
||||
|
||||
// A buffered file with a direct buffer access and no synchronization.
|
||||
class direct_buffered_file {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
enum { buffer_size = 4096 };
|
||||
char buffer_[buffer_size];
|
||||
int pos_;
|
||||
|
||||
void flush() {
|
||||
if (pos_ == 0) return;
|
||||
file_.write(buffer_, pos_);
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||
return _strtod_l(nptr, endptr, loc);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
locale(const locale&) = delete;
|
||||
void operator=(const locale&) = delete;
|
||||
|
||||
locale() {
|
||||
# ifndef _WIN32
|
||||
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||
# else
|
||||
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||
# endif
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
167
include/fmt/ostream.h
Normal file
167
include/fmt/ostream.h
Normal file
@ -0,0 +1,167 @@
|
||||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
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 {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
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);
|
||||
#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());
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
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);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
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;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
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_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
2
include/fmt/posix.h
Normal file
2
include/fmt/posix.h
Normal file
@ -0,0 +1,2 @@
|
||||
#include "os.h"
|
||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
751
include/fmt/printf.h
Normal file
751
include/fmt/printf.h
Normal file
@ -0,0 +1,751 @@
|
||||
// Formatting library for C++ - legacy printf implementation
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
int operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
basic_format_arg<Context>& arg_;
|
||||
char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context>& arg, char_type type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's') operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
|
||||
void operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
|
||||
void operator()(U) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
|
||||
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context> class char_converter {
|
||||
private:
|
||||
basic_format_arg<Context>& arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
private:
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
format_specs& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
template <typename OutputIt, typename Char>
|
||||
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
||||
public:
|
||||
using iterator = OutputIt;
|
||||
|
||||
private:
|
||||
using char_type = Char;
|
||||
using base = detail::arg_formatter_base<OutputIt, Char>;
|
||||
using context_type = basic_printf_context<OutputIt, 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)");
|
||||
}
|
||||
|
||||
public:
|
||||
using format_specs = typename base::format_specs;
|
||||
|
||||
/**
|
||||
\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) {}
|
||||
|
||||
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')
|
||||
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.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// 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 this->out();
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
iterator 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();
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
|
||||
iterator operator()(basic_string_view<char_type> 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();
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
specs.align = align::left;
|
||||
break;
|
||||
case '+':
|
||||
specs.sign = sign::plus;
|
||||
break;
|
||||
case '0':
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
int arg_index = -1;
|
||||
char_type 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);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
} 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.
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(specs, it, end);
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
detail::printf_width_handler<char_type>(specs), get_arg()));
|
||||
}
|
||||
}
|
||||
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();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (it != end && *it == c) {
|
||||
out = std::copy(start, it, out);
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = std::copy(start, it - 1, out);
|
||||
|
||||
format_specs 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");
|
||||
|
||||
// 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);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg 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())
|
||||
specs.fill[0] =
|
||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||
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)));
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||
// flag is also present.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
char_type t = it != end ? *it : 0;
|
||||
using detail::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<signed char>(arg, t);
|
||||
} else {
|
||||
convert_arg<short>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (t == 'l') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<long long>(arg, t);
|
||||
} else {
|
||||
convert_arg<long>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, c);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
||||
}
|
||||
return std::copy(start, it, out);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
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) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
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) {
|
||||
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) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
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) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(format), 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) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
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) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(format),
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
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 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) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
detail::write_buffer(os, buffer);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
\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_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
386
include/fmt/ranges.h
Normal file
386
include/fmt/ranges.h
Normal file
@ -0,0 +1,386 @@
|
||||
// Formatting library for C++ - experimental range support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#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>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
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 U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::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 {};
|
||||
|
||||
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
|
||||
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 {};
|
||||
#endif
|
||||
|
||||
/// 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());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
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 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 ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
|
||||
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"\"{}\"";
|
||||
}
|
||||
|
||||
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"'{}'";
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
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()
|
||||
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);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(
|
||||
std::declval<FormatContext>().out())>::type 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>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, detail::make_index_sequence<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)...);
|
||||
}
|
||||
|
||||
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.
|
||||
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) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
||||
string_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
wstring_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\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) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
253
posix.cc
253
posix.cc
@ -1,253 +0,0 @@
|
||||
/*
|
||||
A C++ interface to POSIX functions.
|
||||
|
||||
Copyright (c) 2014 - 2015, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY 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.
|
||||
*/
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "posix.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# include <windows.h>
|
||||
# include <io.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
|
||||
# undef fileno
|
||||
# endif
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
typedef int RWResult;
|
||||
|
||||
// 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
|
||||
// Return type of read and write functions.
|
||||
typedef ssize_t RWResult;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
}
|
||||
|
||||
fmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
fmt::report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
fmt::BufferedFile::BufferedFile(
|
||||
fmt::CStringRef filename, fmt::CStringRef mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0);
|
||||
if (!file_)
|
||||
throw SystemError(errno, "cannot open file {}", filename);
|
||||
}
|
||||
|
||||
void fmt::BufferedFile::close() {
|
||||
if (!file_)
|
||||
return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = 0;
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot close file");
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
#define FMT_ARGS
|
||||
|
||||
int fmt::BufferedFile::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1)
|
||||
throw SystemError(errno, "cannot get file descriptor");
|
||||
return fd;
|
||||
}
|
||||
|
||||
fmt::File::File(fmt::CStringRef path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
#else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
#endif
|
||||
if (fd_ == -1)
|
||||
throw SystemError(errno, "cannot open file {}", path);
|
||||
}
|
||||
|
||||
fmt::File::~File() FMT_NOEXCEPT {
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
|
||||
fmt::report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
void fmt::File::close() {
|
||||
if (fd_ == -1)
|
||||
return;
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot close file");
|
||||
}
|
||||
|
||||
fmt::LongLong fmt::File::size() const {
|
||||
#ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
// Both functions support large file sizes.
|
||||
DWORD size_upper = 0;
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
|
||||
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
throw WindowsError(GetLastError(), "cannot get file size");
|
||||
}
|
||||
fmt::ULongLong long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
#else
|
||||
typedef struct stat Stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
throw SystemError(errno, "cannot get file attributes");
|
||||
FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size),
|
||||
"return type of File::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t fmt::File::read(void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
throw SystemError(errno, "cannot read from file");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::size_t fmt::File::write(const void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
throw SystemError(errno, "cannot write to file");
|
||||
return result;
|
||||
}
|
||||
|
||||
fmt::File fmt::File::dup(int fd) {
|
||||
// Don't retry as dup doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
throw SystemError(errno, "cannot duplicate file descriptor {}", fd);
|
||||
return File(new_fd);
|
||||
}
|
||||
|
||||
void fmt::File::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
throw SystemError(errno,
|
||||
"cannot duplicate file descriptor {} to {}", fd_, fd);
|
||||
}
|
||||
}
|
||||
|
||||
void fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1)
|
||||
ec = ErrorCode(errno);
|
||||
}
|
||||
|
||||
void fmt::File::pipe(File &read_end, File &write_end) {
|
||||
// Close the descriptors first to make sure that assignments don't throw
|
||||
// and there are no leaks.
|
||||
read_end.close();
|
||||
write_end.close();
|
||||
int fds[2] = {};
|
||||
#ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
enum { DEFAULT_CAPACITY = 65536 };
|
||||
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
|
||||
#else
|
||||
// Don't retry as the pipe function doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
#endif
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot create pipe");
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = File(fds[0]);
|
||||
write_end = File(fds[1]);
|
||||
}
|
||||
|
||||
fmt::BufferedFile fmt::File::fdopen(const char *mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
if (!f)
|
||||
throw SystemError(errno, "cannot associate stream with file descriptor");
|
||||
BufferedFile file(f);
|
||||
fd_ = -1;
|
||||
return file;
|
||||
}
|
||||
|
||||
long fmt::getpagesize() {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
#else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0)
|
||||
throw SystemError(errno, "cannot get memory page size");
|
||||
return size;
|
||||
#endif
|
||||
}
|
344
posix.h
344
posix.h
@ -1,344 +0,0 @@
|
||||
/*
|
||||
A C++ interface to POSIX functions.
|
||||
|
||||
Copyright (c) 2014 - 2015, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY 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.
|
||||
*/
|
||||
|
||||
#ifndef FMT_POSIX_H_
|
||||
#define FMT_POSIX_H_
|
||||
|
||||
#ifdef __MINGW32__
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // for O_RDONLY
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_GCC_VERSION >= 407
|
||||
# define FMT_UNUSED __attribute__((unused))
|
||||
#else
|
||||
# define FMT_UNUSED
|
||||
#endif
|
||||
|
||||
#ifndef FMT_USE_STATIC_ASSERT
|
||||
# define FMT_USE_STATIC_ASSERT 0
|
||||
#endif
|
||||
|
||||
#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \
|
||||
(FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600
|
||||
# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message)
|
||||
#else
|
||||
# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b)
|
||||
# define FMT_STATIC_ASSERT(cond, message) \
|
||||
typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
result = (expression); \
|
||||
} while (result == error_result && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
namespace fmt {
|
||||
|
||||
// An error code.
|
||||
class ErrorCode {
|
||||
private:
|
||||
int value_;
|
||||
|
||||
public:
|
||||
explicit ErrorCode(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
};
|
||||
|
||||
// A buffered file.
|
||||
class BufferedFile {
|
||||
private:
|
||||
FILE *file_;
|
||||
|
||||
friend class File;
|
||||
|
||||
explicit BufferedFile(FILE *f) : file_(f) {}
|
||||
|
||||
public:
|
||||
// Constructs a BufferedFile object which doesn't represent any file.
|
||||
BufferedFile() FMT_NOEXCEPT : file_(0) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
~BufferedFile() FMT_NOEXCEPT;
|
||||
|
||||
#if !FMT_USE_RVALUE_REFERENCES
|
||||
// Emulate a move constructor and a move assignment operator if rvalue
|
||||
// references are not supported.
|
||||
|
||||
private:
|
||||
// A proxy object to emulate a move constructor.
|
||||
// It is private to make it impossible call operator Proxy directly.
|
||||
struct Proxy {
|
||||
FILE *file;
|
||||
};
|
||||
|
||||
public:
|
||||
// A "move constructor" for moving from a temporary.
|
||||
BufferedFile(Proxy p) FMT_NOEXCEPT : file_(p.file) {}
|
||||
|
||||
// A "move constructor" for for moving from an lvalue.
|
||||
BufferedFile(BufferedFile &f) FMT_NOEXCEPT : file_(f.file_) {
|
||||
f.file_ = 0;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from a temporary.
|
||||
BufferedFile &operator=(Proxy p) {
|
||||
close();
|
||||
file_ = p.file;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from an lvalue.
|
||||
BufferedFile &operator=(BufferedFile &other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns a proxy object for moving from a temporary:
|
||||
// BufferedFile file = BufferedFile(...);
|
||||
operator Proxy() FMT_NOEXCEPT {
|
||||
Proxy p = {file_};
|
||||
file_ = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
#else
|
||||
private:
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile);
|
||||
|
||||
public:
|
||||
BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = 0;
|
||||
}
|
||||
|
||||
BufferedFile& operator=(BufferedFile &&other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = 0;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Opens a file.
|
||||
BufferedFile(CStringRef filename, CStringRef mode);
|
||||
|
||||
// Closes the file.
|
||||
void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE *get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
int (fileno)() const;
|
||||
|
||||
void print(CStringRef format_str, const ArgList &args) {
|
||||
fmt::print(file_, format_str, args);
|
||||
}
|
||||
FMT_VARIADIC(void, print, CStringRef)
|
||||
};
|
||||
|
||||
// A file. Closed file is represented by a File object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::SystemError in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class File {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a File object with a given descriptor.
|
||||
explicit File(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
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.
|
||||
};
|
||||
|
||||
// Constructs a File object which doesn't represent any file.
|
||||
File() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a File object representing this file.
|
||||
File(CStringRef path, int oflag);
|
||||
|
||||
#if !FMT_USE_RVALUE_REFERENCES
|
||||
// Emulate a move constructor and a move assignment operator if rvalue
|
||||
// references are not supported.
|
||||
|
||||
private:
|
||||
// A proxy object to emulate a move constructor.
|
||||
// It is private to make it impossible call operator Proxy directly.
|
||||
struct Proxy {
|
||||
int fd;
|
||||
};
|
||||
|
||||
public:
|
||||
// A "move constructor" for moving from a temporary.
|
||||
File(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {}
|
||||
|
||||
// A "move constructor" for for moving from an lvalue.
|
||||
File(File &other) FMT_NOEXCEPT : fd_(other.fd_) {
|
||||
other.fd_ = -1;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from a temporary.
|
||||
File &operator=(Proxy p) {
|
||||
close();
|
||||
fd_ = p.fd;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from an lvalue.
|
||||
File &operator=(File &other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns a proxy object for moving from a temporary:
|
||||
// File file = File(...);
|
||||
operator Proxy() FMT_NOEXCEPT {
|
||||
Proxy p = {fd_};
|
||||
fd_ = -1;
|
||||
return p;
|
||||
}
|
||||
|
||||
#else
|
||||
private:
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(File);
|
||||
|
||||
public:
|
||||
File(File &&other) FMT_NOEXCEPT : fd_(other.fd_) {
|
||||
other.fd_ = -1;
|
||||
}
|
||||
|
||||
File& operator=(File &&other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
~File() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
void close();
|
||||
|
||||
// Returns the file size.
|
||||
LongLong size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
std::size_t read(void *buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
std::size_t write(const void *buffer, std::size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
static File dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
static void pipe(File &read_end, File &write_end);
|
||||
|
||||
// Creates a BufferedFile object associated with this file and detaches
|
||||
// this File object from the file.
|
||||
BufferedFile fdopen(const char *mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
} // namespace fmt
|
||||
|
||||
#if !FMT_USE_RVALUE_REFERENCES
|
||||
namespace std {
|
||||
// For compatibility with C++98.
|
||||
inline fmt::BufferedFile &move(fmt::BufferedFile &f) { return f; }
|
||||
inline fmt::File &move(fmt::File &f) { return f; }
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // FMT_POSIX_H_
|
68
src/format.cc
Normal file
68
src/format.cc
Normal file
@ -0,0 +1,68 @@
|
||||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
#endif
|
||||
// Suppress the warning about nonliteral format string.
|
||||
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
|
||||
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>;
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, detail::float_specs,
|
||||
detail::buffer<char>&) = detail::format_float;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||
#endif
|
||||
|
||||
// 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 char detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API format_context::iterator detail::vformat_to(
|
||||
detail::buffer<char>&, string_view, basic_format_args<format_context>);
|
||||
|
||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::snprintf_float(long double, int,
|
||||
detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
|
||||
// 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 wchar_t detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
FMT_END_NAMESPACE
|
317
src/os.cc
Normal file
317
src/os.cc
Normal file
@ -0,0 +1,317 @@
|
||||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/os.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# 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
|
||||
# endif
|
||||
# endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
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
|
||||
// Return type of read and write functions.
|
||||
using RWResult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
detail::utf16_to_utf8::utf16_to_utf8(wstring_view 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) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
|
||||
length, nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
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));
|
||||
}
|
||||
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
string_view 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;
|
||||
}
|
||||
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 {
|
||||
report_error(detail::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
buffered_file::~buffered_file() FMT_NOEXCEPT {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||
nullptr);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_) return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = nullptr;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
#define FMT_ARGS
|
||||
|
||||
int buffered_file::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
# else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
# endif
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
}
|
||||
|
||||
file::~file() FMT_NOEXCEPT {
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
void file::close() {
|
||||
if (fd_ == -1) return;
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
# ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
// Both functions support large file sizes.
|
||||
DWORD size_upper = 0;
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
|
||||
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
||||
}
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
# else
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
# endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
file file::dup(int fd) {
|
||||
// Don't retry as dup doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
void file::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
|
||||
fd_, fd));
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) ec = error_code(errno);
|
||||
}
|
||||
|
||||
void file::pipe(file& read_end, file& write_end) {
|
||||
// Close the descriptors first to make sure that assignments don't throw
|
||||
// and there are no leaks.
|
||||
read_end.close();
|
||||
write_end.close();
|
||||
int fds[2] = {};
|
||||
# ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
enum { DEFAULT_CAPACITY = 65536 };
|
||||
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
|
||||
# else
|
||||
// Don't retry as the pipe function doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
# endif
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
#if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
#else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
#endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
long getpagesize() {
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
# else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
@ -1,10 +1,10 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := cppformat_static
|
||||
LOCAL_MODULE_FILENAME := libcppformat
|
||||
LOCAL_MODULE := fmt_static
|
||||
LOCAL_MODULE_FILENAME := libfmt
|
||||
|
||||
LOCAL_SRC_FILES := format.cc
|
||||
LOCAL_SRC_FILES := ../src/format.cc
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
|
1
support/AndroidManifest.xml
Normal file
1
support/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest package="net.fmtlib" />
|
2061
support/C++.sublime-syntax
Normal file
2061
support/C++.sublime-syntax
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,3 +2,5 @@ This directory contains build support files such as
|
||||
|
||||
* CMake modules
|
||||
* Build scripts
|
||||
* qmake (static build with dynamic libc only)
|
||||
|
||||
|
20
support/Vagrantfile
vendored
Normal file
20
support/Vagrantfile
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# A vagrant config for testing against gcc-4.8.
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
config.disksize.size = '15GB'
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = "4096"
|
||||
end
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
apt-get update
|
||||
apt-get install -y g++ make wget git
|
||||
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz
|
||||
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
|
||||
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
|
||||
SHELL
|
||||
end
|
45
support/appveyor-build.py
Executable file → Normal file
45
support/appveyor-build.py
Executable file → Normal file
@ -6,26 +6,37 @@ from subprocess import check_call
|
||||
|
||||
build = os.environ['BUILD']
|
||||
config = os.environ['CONFIGURATION']
|
||||
platform = os.environ.get('PLATFORM')
|
||||
platform = os.environ['PLATFORM']
|
||||
path = os.environ['PATH']
|
||||
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config]
|
||||
image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
|
||||
jobid = os.environ['APPVEYOR_JOB_ID']
|
||||
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..']
|
||||
if build == 'mingw':
|
||||
cmake_command.append('-GMinGW Makefiles')
|
||||
build_command = ['mingw32-make', '-j4']
|
||||
test_command = ['mingw32-make', 'test']
|
||||
# Remove the path to Git bin directory from $PATH because it breaks MinGW config.
|
||||
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
|
||||
os.environ['PATH'] = r'C:\MinGW\bin;' + path
|
||||
cmake_command.append('-GMinGW Makefiles')
|
||||
build_command = ['mingw32-make', '-j4']
|
||||
test_command = ['mingw32-make', 'test']
|
||||
# Remove the path to Git bin directory from $PATH because it breaks
|
||||
# MinGW config.
|
||||
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
|
||||
os.environ['PATH'] = r'C:\MinGW\bin;' + path
|
||||
else:
|
||||
# Add MSBuild 14.0 to PATH as described in
|
||||
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
|
||||
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\14.0\Bin;' + path
|
||||
generator = 'Visual Studio 14 2015'
|
||||
if platform == 'x64':
|
||||
generator += ' Win64'
|
||||
cmake_command.append('-G' + generator)
|
||||
build_command = ['msbuild', '/m:4', '/p:Config=' + config, 'FORMAT.sln']
|
||||
test_command = ['msbuild', 'RUN_TESTS.vcxproj']
|
||||
# Add MSBuild 14.0 to PATH as described in
|
||||
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
|
||||
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path
|
||||
if image == 'Visual Studio 2019':
|
||||
generator = 'Visual Studio 16 2019'
|
||||
if platform == 'x64':
|
||||
cmake_command.extend(['-A', 'x64'])
|
||||
else:
|
||||
if image == 'Visual Studio 2015':
|
||||
generator = 'Visual Studio 14 2015'
|
||||
elif image == 'Visual Studio 2017':
|
||||
generator = 'Visual Studio 15 2017'
|
||||
if platform == 'x64':
|
||||
generator += ' Win64'
|
||||
cmake_command.append('-G' + generator)
|
||||
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
|
||||
test_command = ['ctest', '-C', config]
|
||||
|
||||
check_call(cmake_command)
|
||||
check_call(build_command)
|
||||
|
@ -2,21 +2,40 @@ configuration:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
image:
|
||||
- Visual Studio 2015
|
||||
- Visual Studio 2019
|
||||
- Visual Studio 2017
|
||||
|
||||
platform:
|
||||
- Win32
|
||||
- x64
|
||||
|
||||
environment:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
matrix:
|
||||
- BUILD: msvc
|
||||
- BUILD: msvc
|
||||
PLATFORM: x64
|
||||
- BUILD: mingw
|
||||
MSVC_DEFAULT_OPTIONS: ON
|
||||
BUILD: msvc
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- image: Visual Studio 2015
|
||||
platform: Win32
|
||||
- image: Visual Studio 2019
|
||||
platform: Win32
|
||||
|
||||
before_build:
|
||||
# Workaround for CMake not wanting sh.exe on PATH for MinGW.
|
||||
- set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
||||
build_script:
|
||||
- python support/appveyor-build.py
|
||||
- python ../support/appveyor-build.py
|
||||
|
||||
on_failure:
|
||||
- appveyor PushArtifact Testing/Temporary/LastTest.log
|
||||
- appveyor AddTest test
|
||||
|
||||
# Uncomment this to debug AppVeyor failures.
|
||||
#on_finish:
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Build the project with Biicode.
|
||||
|
||||
import glob, os, shutil
|
||||
from subprocess import check_call
|
||||
|
||||
project_dir = 'biicode_project'
|
||||
check_call(['bii', 'init', project_dir])
|
||||
cppformat_dir = os.path.join(project_dir, 'blocks/vitaut/cppformat')
|
||||
shutil.copytree('.', cppformat_dir, ignore=shutil.ignore_patterns(project_dir))
|
||||
for f in glob.glob('support/biicode/*'):
|
||||
shutil.copy(f, cppformat_dir)
|
||||
check_call(['bii', 'cpp:build'], cwd=project_dir)
|
@ -1,19 +0,0 @@
|
||||
# Biicode configuration file
|
||||
|
||||
[paths]
|
||||
# Local directories to look for headers (within block)
|
||||
/
|
||||
|
||||
[dependencies]
|
||||
# Manual adjust file implicit dependencies, add (+), remove (-), or overwrite (=)
|
||||
CMakeLists.txt + cmake/FindSetEnv.cmake
|
||||
format.h = format.cc
|
||||
format.cc - test/* posix.cc
|
||||
support/biicode/sample.cc - test/*
|
||||
|
||||
[mains]
|
||||
# Manual adjust of files that define an executable
|
||||
!test/test-main.cc
|
||||
|
||||
[parent]
|
||||
vitaut/cppformat: 0
|
@ -1,3 +0,0 @@
|
||||
doc/*
|
||||
breathe/*
|
||||
gmock/*
|
@ -1,17 +0,0 @@
|
||||
#include "vitaut/cppformat/format.h"
|
||||
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Date &d) {
|
||||
return os << d.year_ << '-' << d.month_ << '-' << d.day_;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
std::string s = fmt::format("The date is {}", Date(2012, 12, 9));
|
||||
fmt::print("Hello, {}!", "world"); // uses Python-like format string syntax
|
||||
fmt::printf("\n%s", s); // uses printf format string syntax
|
||||
}
|
107
support/build.gradle
Normal file
107
support/build.gradle
Normal file
@ -0,0 +1,107 @@
|
||||
|
||||
// General gradle arguments for root project
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
//
|
||||
// https://developer.android.com/studio/releases/gradle-plugin
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
// Output: Shared library (.so) for Android
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25 // Android 7.0
|
||||
|
||||
// Target ABI
|
||||
// - This option controls target platform of module
|
||||
// - The platform might be limited by compiler's support
|
||||
// some can work with Clang(default), but some can work only with GCC...
|
||||
// if bad, both toolchains might not support it
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
// Specify platforms for Application
|
||||
reset()
|
||||
include "arm64-v8a", "armeabi-v7a", "x86_64"
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_STL=c++_shared" // Specify Android STL
|
||||
arguments "-DBUILD_SHARED_LIBS=true" // Build shared object
|
||||
arguments "-DFMT_TEST=false" // Skip test
|
||||
arguments "-DFMT_DOC=false" // Skip document
|
||||
cppFlags "-std=c++17"
|
||||
}
|
||||
}
|
||||
println("Gradle CMake Plugin: ")
|
||||
println(externalNativeBuild.cmake.cppFlags)
|
||||
println(externalNativeBuild.cmake.arguments)
|
||||
}
|
||||
|
||||
// External Native build
|
||||
// - Use existing CMakeList.txt
|
||||
// - Give path to CMake. This gradle file should be
|
||||
// neighbor of the top level cmake
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "../CMakeLists.txt"
|
||||
// buildStagingDirectory "./build" // Custom path for cmake output
|
||||
}
|
||||
//println(cmake.path)
|
||||
}
|
||||
|
||||
sourceSets{
|
||||
// Android Manifest for Gradle
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
// Copy debug binaries
|
||||
copy {
|
||||
from '../libs/debug/obj'
|
||||
into '../libs/debug'
|
||||
}
|
||||
// Copy Release binaries
|
||||
copy {
|
||||
from '../libs/release/obj'
|
||||
into '../libs/release'
|
||||
}
|
||||
// Remove empty directory
|
||||
delete '../libs/debug/obj'
|
||||
delete '../libs/release/obj'
|
||||
}
|
26
support/cmake/JoinPaths.cmake
Normal file
26
support/cmake/JoinPaths.cmake
Normal file
@ -0,0 +1,26 @@
|
||||
# This module provides function for joining paths
|
||||
# known from from most languages
|
||||
#
|
||||
# Original license:
|
||||
# SPDX-License-Identifier: (MIT OR CC0-1.0)
|
||||
# Explicit permission given to distribute this module under
|
||||
# the terms of the project as described in /LICENSE.rst.
|
||||
# Copyright 2020 Jan Tojnar
|
||||
# https://github.com/jtojnar/cmake-snips
|
||||
#
|
||||
# Modelled after Python’s os.path.join
|
||||
# https://docs.python.org/3.7/library/os.path.html#os.path.join
|
||||
# Windows not supported
|
||||
function(join_paths joined_path first_path_segment)
|
||||
set(temp_path "${first_path_segment}")
|
||||
foreach(current_segment IN LISTS ARGN)
|
||||
if(NOT ("${current_segment}" STREQUAL ""))
|
||||
if(IS_ABSOLUTE "${current_segment}")
|
||||
set(temp_path "${current_segment}")
|
||||
else()
|
||||
set(temp_path "${temp_path}/${current_segment}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
set(${joined_path} "${temp_path}" PARENT_SCOPE)
|
||||
endfunction()
|
@ -1,18 +0,0 @@
|
||||
# Initializes block variables
|
||||
INIT_BIICODE_BLOCK()
|
||||
|
||||
# Actually create targets: EXEcutables and libraries.
|
||||
ADD_BIICODE_TARGETS()
|
||||
|
||||
target_include_directories(${BII_BLOCK_TARGET} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
if (HAVE_OPEN)
|
||||
target_compile_definitions(${BII_BLOCK_TARGET} INTERFACE -DFMT_USE_FILE_DESCRIPTORS=1)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(${BII_BLOCK_TARGET} INTERFACE -Wall -Wextra -Wshadow -pedantic)
|
||||
endif ()
|
||||
if (CPP11_FLAG AND FMT_PEDANTIC)
|
||||
target_compile_options(${BII_BLOCK_TARGET} INTERFACE ${CPP11_FLAG})
|
||||
endif ()
|
70
support/cmake/cxx14.cmake
Normal file
70
support/cmake/cxx14.cmake
Normal file
@ -0,0 +1,70 @@
|
||||
# C++14 feature support detection
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if (NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
|
||||
|
||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
||||
check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
|
||||
check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
|
||||
|
||||
if (has_std_20_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++20)
|
||||
elseif (has_std_2a_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++2a)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 17)
|
||||
check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
|
||||
check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
|
||||
|
||||
if (has_std_17_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++17)
|
||||
elseif (has_std_1z_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1z)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 14)
|
||||
check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
|
||||
check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
|
||||
|
||||
if (has_std_14_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++14)
|
||||
elseif (has_std_1y_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1y)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 11)
|
||||
check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
|
||||
check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
|
||||
|
||||
if (has_std_11_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++11)
|
||||
elseif (has_std_0x_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++0x)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
|
||||
|
||||
# Check if user-defined literals are available
|
||||
check_cxx_source_compiles("
|
||||
void operator\"\" _udl(long double);
|
||||
int main() {}"
|
||||
SUPPORTS_USER_DEFINED_LITERALS)
|
||||
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
|
||||
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
|
||||
endif ()
|
||||
|
||||
# Check if <variant> is available
|
||||
set(CMAKE_REQUIRED_FLAGS -std=c++1z)
|
||||
check_cxx_source_compiles("
|
||||
#include <variant>
|
||||
int main() {}"
|
||||
FMT_HAS_VARIANT)
|
||||
if (NOT FMT_HAS_VARIANT)
|
||||
set (FMT_HAS_VARIANT OFF)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS )
|
4
support/cmake/fmt-config.cmake.in
Normal file
4
support/cmake/fmt-config.cmake.in
Normal file
@ -0,0 +1,4 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
|
||||
check_required_components(fmt)
|
11
support/cmake/fmt.pc.in
Normal file
11
support/cmake/fmt.pc.in
Normal file
@ -0,0 +1,11 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=@libdir_for_pc_file@
|
||||
includedir=@includedir_for_pc_file@
|
||||
|
||||
Name: fmt
|
||||
Description: A modern formatting library
|
||||
Version: @FMT_VERSION@
|
||||
Libs: -L${libdir} -l@FMT_LIB_NAME@
|
||||
Cflags: -I${includedir}
|
||||
|
@ -1,11 +0,0 @@
|
||||
@echo on
|
||||
rem This scripts configures build environment and runs CMake.
|
||||
rem Use it instead of running CMake directly when building with
|
||||
rem the Microsoft SDK toolchain rather than Visual Studio.
|
||||
rem It is used in the same way as cmake, for example:
|
||||
rem
|
||||
rem run-cmake -G "Visual Studio 10 Win64" .
|
||||
|
||||
for /F "delims=" %%i IN ('cmake "-DPRINT_PATH=1" -P %~dp0/FindSetEnv.cmake') DO set setenv=%%i
|
||||
if NOT "%setenv%" == "" call "%setenv%"
|
||||
cmake %*
|
53
support/compute-powers.py
Executable file
53
support/compute-powers.py
Executable file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
# Compute 10 ** exp with exp in the range [min_exponent, max_exponent] and print
|
||||
# normalized (with most-significant bit equal to 1) significands in hexadecimal.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
min_exponent = -348
|
||||
max_exponent = 340
|
||||
step = 8
|
||||
significand_size = 64
|
||||
exp_offset = 2000
|
||||
|
||||
class fp:
|
||||
pass
|
||||
|
||||
powers = []
|
||||
for i, exp in enumerate(range(min_exponent, max_exponent + 1, step)):
|
||||
result = fp()
|
||||
n = 10 ** exp if exp >= 0 else 2 ** exp_offset / 10 ** -exp
|
||||
k = significand_size + 1
|
||||
# Convert to binary and round.
|
||||
binary = '{:b}'.format(n)
|
||||
result.f = (int('{:0<{}}'.format(binary[:k], k), 2) + 1) / 2
|
||||
result.e = len(binary) - (exp_offset if exp < 0 else 0) - significand_size
|
||||
powers.append(result)
|
||||
# Sanity check.
|
||||
exp_offset10 = 400
|
||||
actual = result.f * 10 ** exp_offset10
|
||||
if result.e > 0:
|
||||
actual *= 2 ** result.e
|
||||
else:
|
||||
for j in range(-result.e):
|
||||
actual /= 2
|
||||
expected = 10 ** (exp_offset10 + exp)
|
||||
precision = len('{}'.format(expected)) - len('{}'.format(actual - expected))
|
||||
if precision < 19:
|
||||
print('low precision:', precision)
|
||||
exit(1)
|
||||
|
||||
print('Significands:', end='')
|
||||
for i, fp in enumerate(powers):
|
||||
if i % 3 == 0:
|
||||
print(end='\n ')
|
||||
print(' {:0<#16x}'.format(fp.f, ), end=',')
|
||||
|
||||
print('\n\nExponents:', end='')
|
||||
for i, fp in enumerate(powers):
|
||||
if i % 11 == 0:
|
||||
print(end='\n ')
|
||||
print(' {:5}'.format(fp.e), end=',')
|
||||
|
||||
print('\n\nMax exponent difference:',
|
||||
max([x.e - powers[i - 1].e for i, x in enumerate(powers)][1:]))
|
581
support/docopt.py
Normal file
581
support/docopt.py
Normal file
@ -0,0 +1,581 @@
|
||||
"""Pythonic command-line interface parser that will make you smile.
|
||||
|
||||
* http://docopt.org
|
||||
* Repository and issue-tracker: https://github.com/docopt/docopt
|
||||
* Licensed under terms of MIT license (see LICENSE-MIT)
|
||||
* Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com
|
||||
|
||||
"""
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
__all__ = ['docopt']
|
||||
__version__ = '0.6.1'
|
||||
|
||||
|
||||
class DocoptLanguageError(Exception):
|
||||
|
||||
"""Error in construction of usage-message by developer."""
|
||||
|
||||
|
||||
class DocoptExit(SystemExit):
|
||||
|
||||
"""Exit in case user invoked program with incorrect arguments."""
|
||||
|
||||
usage = ''
|
||||
|
||||
def __init__(self, message=''):
|
||||
SystemExit.__init__(self, (message + '\n' + self.usage).strip())
|
||||
|
||||
|
||||
class Pattern(object):
|
||||
|
||||
def __eq__(self, other):
|
||||
return repr(self) == repr(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def fix(self):
|
||||
self.fix_identities()
|
||||
self.fix_repeating_arguments()
|
||||
return self
|
||||
|
||||
def fix_identities(self, uniq=None):
|
||||
"""Make pattern-tree tips point to same object if they are equal."""
|
||||
if not hasattr(self, 'children'):
|
||||
return self
|
||||
uniq = list(set(self.flat())) if uniq is None else uniq
|
||||
for i, child in enumerate(self.children):
|
||||
if not hasattr(child, 'children'):
|
||||
assert child in uniq
|
||||
self.children[i] = uniq[uniq.index(child)]
|
||||
else:
|
||||
child.fix_identities(uniq)
|
||||
|
||||
def fix_repeating_arguments(self):
|
||||
"""Fix elements that should accumulate/increment values."""
|
||||
either = [list(child.children) for child in transform(self).children]
|
||||
for case in either:
|
||||
for e in [child for child in case if case.count(child) > 1]:
|
||||
if type(e) is Argument or type(e) is Option and e.argcount:
|
||||
if e.value is None:
|
||||
e.value = []
|
||||
elif type(e.value) is not list:
|
||||
e.value = e.value.split()
|
||||
if type(e) is Command or type(e) is Option and e.argcount == 0:
|
||||
e.value = 0
|
||||
return self
|
||||
|
||||
|
||||
def transform(pattern):
|
||||
"""Expand pattern into an (almost) equivalent one, but with single Either.
|
||||
|
||||
Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d)
|
||||
Quirks: [-a] => (-a), (-a...) => (-a -a)
|
||||
|
||||
"""
|
||||
result = []
|
||||
groups = [[pattern]]
|
||||
while groups:
|
||||
children = groups.pop(0)
|
||||
parents = [Required, Optional, OptionsShortcut, Either, OneOrMore]
|
||||
if any(t in map(type, children) for t in parents):
|
||||
child = [c for c in children if type(c) in parents][0]
|
||||
children.remove(child)
|
||||
if type(child) is Either:
|
||||
for c in child.children:
|
||||
groups.append([c] + children)
|
||||
elif type(child) is OneOrMore:
|
||||
groups.append(child.children * 2 + children)
|
||||
else:
|
||||
groups.append(child.children + children)
|
||||
else:
|
||||
result.append(children)
|
||||
return Either(*[Required(*e) for e in result])
|
||||
|
||||
|
||||
class LeafPattern(Pattern):
|
||||
|
||||
"""Leaf/terminal node of a pattern tree."""
|
||||
|
||||
def __init__(self, name, value=None):
|
||||
self.name, self.value = name, value
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value)
|
||||
|
||||
def flat(self, *types):
|
||||
return [self] if not types or type(self) in types else []
|
||||
|
||||
def match(self, left, collected=None):
|
||||
collected = [] if collected is None else collected
|
||||
pos, match = self.single_match(left)
|
||||
if match is None:
|
||||
return False, left, collected
|
||||
left_ = left[:pos] + left[pos + 1:]
|
||||
same_name = [a for a in collected if a.name == self.name]
|
||||
if type(self.value) in (int, list):
|
||||
if type(self.value) is int:
|
||||
increment = 1
|
||||
else:
|
||||
increment = ([match.value] if type(match.value) is str
|
||||
else match.value)
|
||||
if not same_name:
|
||||
match.value = increment
|
||||
return True, left_, collected + [match]
|
||||
same_name[0].value += increment
|
||||
return True, left_, collected
|
||||
return True, left_, collected + [match]
|
||||
|
||||
|
||||
class BranchPattern(Pattern):
|
||||
|
||||
"""Branch/inner node of a pattern tree."""
|
||||
|
||||
def __init__(self, *children):
|
||||
self.children = list(children)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__,
|
||||
', '.join(repr(a) for a in self.children))
|
||||
|
||||
def flat(self, *types):
|
||||
if type(self) in types:
|
||||
return [self]
|
||||
return sum([child.flat(*types) for child in self.children], [])
|
||||
|
||||
|
||||
class Argument(LeafPattern):
|
||||
|
||||
def single_match(self, left):
|
||||
for n, pattern in enumerate(left):
|
||||
if type(pattern) is Argument:
|
||||
return n, Argument(self.name, pattern.value)
|
||||
return None, None
|
||||
|
||||
@classmethod
|
||||
def parse(class_, source):
|
||||
name = re.findall('(<\S*?>)', source)[0]
|
||||
value = re.findall('\[default: (.*)\]', source, flags=re.I)
|
||||
return class_(name, value[0] if value else None)
|
||||
|
||||
|
||||
class Command(Argument):
|
||||
|
||||
def __init__(self, name, value=False):
|
||||
self.name, self.value = name, value
|
||||
|
||||
def single_match(self, left):
|
||||
for n, pattern in enumerate(left):
|
||||
if type(pattern) is Argument:
|
||||
if pattern.value == self.name:
|
||||
return n, Command(self.name, True)
|
||||
else:
|
||||
break
|
||||
return None, None
|
||||
|
||||
|
||||
class Option(LeafPattern):
|
||||
|
||||
def __init__(self, short=None, long=None, argcount=0, value=False):
|
||||
assert argcount in (0, 1)
|
||||
self.short, self.long, self.argcount = short, long, argcount
|
||||
self.value = None if value is False and argcount else value
|
||||
|
||||
@classmethod
|
||||
def parse(class_, option_description):
|
||||
short, long, argcount, value = None, None, 0, False
|
||||
options, _, description = option_description.strip().partition(' ')
|
||||
options = options.replace(',', ' ').replace('=', ' ')
|
||||
for s in options.split():
|
||||
if s.startswith('--'):
|
||||
long = s
|
||||
elif s.startswith('-'):
|
||||
short = s
|
||||
else:
|
||||
argcount = 1
|
||||
if argcount:
|
||||
matched = re.findall('\[default: (.*)\]', description, flags=re.I)
|
||||
value = matched[0] if matched else None
|
||||
return class_(short, long, argcount, value)
|
||||
|
||||
def single_match(self, left):
|
||||
for n, pattern in enumerate(left):
|
||||
if self.name == pattern.name:
|
||||
return n, pattern
|
||||
return None, None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.long or self.short
|
||||
|
||||
def __repr__(self):
|
||||
return 'Option(%r, %r, %r, %r)' % (self.short, self.long,
|
||||
self.argcount, self.value)
|
||||
|
||||
|
||||
class Required(BranchPattern):
|
||||
|
||||
def match(self, left, collected=None):
|
||||
collected = [] if collected is None else collected
|
||||
l = left
|
||||
c = collected
|
||||
for pattern in self.children:
|
||||
matched, l, c = pattern.match(l, c)
|
||||
if not matched:
|
||||
return False, left, collected
|
||||
return True, l, c
|
||||
|
||||
|
||||
class Optional(BranchPattern):
|
||||
|
||||
def match(self, left, collected=None):
|
||||
collected = [] if collected is None else collected
|
||||
for pattern in self.children:
|
||||
m, left, collected = pattern.match(left, collected)
|
||||
return True, left, collected
|
||||
|
||||
|
||||
class OptionsShortcut(Optional):
|
||||
|
||||
"""Marker/placeholder for [options] shortcut."""
|
||||
|
||||
|
||||
class OneOrMore(BranchPattern):
|
||||
|
||||
def match(self, left, collected=None):
|
||||
assert len(self.children) == 1
|
||||
collected = [] if collected is None else collected
|
||||
l = left
|
||||
c = collected
|
||||
l_ = None
|
||||
matched = True
|
||||
times = 0
|
||||
while matched:
|
||||
# could it be that something didn't match but changed l or c?
|
||||
matched, l, c = self.children[0].match(l, c)
|
||||
times += 1 if matched else 0
|
||||
if l_ == l:
|
||||
break
|
||||
l_ = l
|
||||
if times >= 1:
|
||||
return True, l, c
|
||||
return False, left, collected
|
||||
|
||||
|
||||
class Either(BranchPattern):
|
||||
|
||||
def match(self, left, collected=None):
|
||||
collected = [] if collected is None else collected
|
||||
outcomes = []
|
||||
for pattern in self.children:
|
||||
matched, _, _ = outcome = pattern.match(left, collected)
|
||||
if matched:
|
||||
outcomes.append(outcome)
|
||||
if outcomes:
|
||||
return min(outcomes, key=lambda outcome: len(outcome[1]))
|
||||
return False, left, collected
|
||||
|
||||
|
||||
class Tokens(list):
|
||||
|
||||
def __init__(self, source, error=DocoptExit):
|
||||
self += source.split() if hasattr(source, 'split') else source
|
||||
self.error = error
|
||||
|
||||
@staticmethod
|
||||
def from_pattern(source):
|
||||
source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source)
|
||||
source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s]
|
||||
return Tokens(source, error=DocoptLanguageError)
|
||||
|
||||
def move(self):
|
||||
return self.pop(0) if len(self) else None
|
||||
|
||||
def current(self):
|
||||
return self[0] if len(self) else None
|
||||
|
||||
|
||||
def parse_long(tokens, options):
|
||||
"""long ::= '--' chars [ ( ' ' | '=' ) chars ] ;"""
|
||||
long, eq, value = tokens.move().partition('=')
|
||||
assert long.startswith('--')
|
||||
value = None if eq == value == '' else value
|
||||
similar = [o for o in options if o.long == long]
|
||||
if tokens.error is DocoptExit and similar == []: # if no exact match
|
||||
similar = [o for o in options if o.long and o.long.startswith(long)]
|
||||
if len(similar) > 1: # might be simply specified ambiguously 2+ times?
|
||||
raise tokens.error('%s is not a unique prefix: %s?' %
|
||||
(long, ', '.join(o.long for o in similar)))
|
||||
elif len(similar) < 1:
|
||||
argcount = 1 if eq == '=' else 0
|
||||
o = Option(None, long, argcount)
|
||||
options.append(o)
|
||||
if tokens.error is DocoptExit:
|
||||
o = Option(None, long, argcount, value if argcount else True)
|
||||
else:
|
||||
o = Option(similar[0].short, similar[0].long,
|
||||
similar[0].argcount, similar[0].value)
|
||||
if o.argcount == 0:
|
||||
if value is not None:
|
||||
raise tokens.error('%s must not have an argument' % o.long)
|
||||
else:
|
||||
if value is None:
|
||||
if tokens.current() in [None, '--']:
|
||||
raise tokens.error('%s requires argument' % o.long)
|
||||
value = tokens.move()
|
||||
if tokens.error is DocoptExit:
|
||||
o.value = value if value is not None else True
|
||||
return [o]
|
||||
|
||||
|
||||
def parse_shorts(tokens, options):
|
||||
"""shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;"""
|
||||
token = tokens.move()
|
||||
assert token.startswith('-') and not token.startswith('--')
|
||||
left = token.lstrip('-')
|
||||
parsed = []
|
||||
while left != '':
|
||||
short, left = '-' + left[0], left[1:]
|
||||
similar = [o for o in options if o.short == short]
|
||||
if len(similar) > 1:
|
||||
raise tokens.error('%s is specified ambiguously %d times' %
|
||||
(short, len(similar)))
|
||||
elif len(similar) < 1:
|
||||
o = Option(short, None, 0)
|
||||
options.append(o)
|
||||
if tokens.error is DocoptExit:
|
||||
o = Option(short, None, 0, True)
|
||||
else: # why copying is necessary here?
|
||||
o = Option(short, similar[0].long,
|
||||
similar[0].argcount, similar[0].value)
|
||||
value = None
|
||||
if o.argcount != 0:
|
||||
if left == '':
|
||||
if tokens.current() in [None, '--']:
|
||||
raise tokens.error('%s requires argument' % short)
|
||||
value = tokens.move()
|
||||
else:
|
||||
value = left
|
||||
left = ''
|
||||
if tokens.error is DocoptExit:
|
||||
o.value = value if value is not None else True
|
||||
parsed.append(o)
|
||||
return parsed
|
||||
|
||||
|
||||
def parse_pattern(source, options):
|
||||
tokens = Tokens.from_pattern(source)
|
||||
result = parse_expr(tokens, options)
|
||||
if tokens.current() is not None:
|
||||
raise tokens.error('unexpected ending: %r' % ' '.join(tokens))
|
||||
return Required(*result)
|
||||
|
||||
|
||||
def parse_expr(tokens, options):
|
||||
"""expr ::= seq ( '|' seq )* ;"""
|
||||
seq = parse_seq(tokens, options)
|
||||
if tokens.current() != '|':
|
||||
return seq
|
||||
result = [Required(*seq)] if len(seq) > 1 else seq
|
||||
while tokens.current() == '|':
|
||||
tokens.move()
|
||||
seq = parse_seq(tokens, options)
|
||||
result += [Required(*seq)] if len(seq) > 1 else seq
|
||||
return [Either(*result)] if len(result) > 1 else result
|
||||
|
||||
|
||||
def parse_seq(tokens, options):
|
||||
"""seq ::= ( atom [ '...' ] )* ;"""
|
||||
result = []
|
||||
while tokens.current() not in [None, ']', ')', '|']:
|
||||
atom = parse_atom(tokens, options)
|
||||
if tokens.current() == '...':
|
||||
atom = [OneOrMore(*atom)]
|
||||
tokens.move()
|
||||
result += atom
|
||||
return result
|
||||
|
||||
|
||||
def parse_atom(tokens, options):
|
||||
"""atom ::= '(' expr ')' | '[' expr ']' | 'options'
|
||||
| long | shorts | argument | command ;
|
||||
"""
|
||||
token = tokens.current()
|
||||
result = []
|
||||
if token in '([':
|
||||
tokens.move()
|
||||
matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token]
|
||||
result = pattern(*parse_expr(tokens, options))
|
||||
if tokens.move() != matching:
|
||||
raise tokens.error("unmatched '%s'" % token)
|
||||
return [result]
|
||||
elif token == 'options':
|
||||
tokens.move()
|
||||
return [OptionsShortcut()]
|
||||
elif token.startswith('--') and token != '--':
|
||||
return parse_long(tokens, options)
|
||||
elif token.startswith('-') and token not in ('-', '--'):
|
||||
return parse_shorts(tokens, options)
|
||||
elif token.startswith('<') and token.endswith('>') or token.isupper():
|
||||
return [Argument(tokens.move())]
|
||||
else:
|
||||
return [Command(tokens.move())]
|
||||
|
||||
|
||||
def parse_argv(tokens, options, options_first=False):
|
||||
"""Parse command-line argument vector.
|
||||
|
||||
If options_first:
|
||||
argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ;
|
||||
else:
|
||||
argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ;
|
||||
|
||||
"""
|
||||
parsed = []
|
||||
while tokens.current() is not None:
|
||||
if tokens.current() == '--':
|
||||
return parsed + [Argument(None, v) for v in tokens]
|
||||
elif tokens.current().startswith('--'):
|
||||
parsed += parse_long(tokens, options)
|
||||
elif tokens.current().startswith('-') and tokens.current() != '-':
|
||||
parsed += parse_shorts(tokens, options)
|
||||
elif options_first:
|
||||
return parsed + [Argument(None, v) for v in tokens]
|
||||
else:
|
||||
parsed.append(Argument(None, tokens.move()))
|
||||
return parsed
|
||||
|
||||
|
||||
def parse_defaults(doc):
|
||||
defaults = []
|
||||
for s in parse_section('options:', doc):
|
||||
# FIXME corner case "bla: options: --foo"
|
||||
_, _, s = s.partition(':') # get rid of "options:"
|
||||
split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:]
|
||||
split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])]
|
||||
options = [Option.parse(s) for s in split if s.startswith('-')]
|
||||
defaults += options
|
||||
return defaults
|
||||
|
||||
|
||||
def parse_section(name, source):
|
||||
pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)',
|
||||
re.IGNORECASE | re.MULTILINE)
|
||||
return [s.strip() for s in pattern.findall(source)]
|
||||
|
||||
|
||||
def formal_usage(section):
|
||||
_, _, section = section.partition(':') # drop "usage:"
|
||||
pu = section.split()
|
||||
return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )'
|
||||
|
||||
|
||||
def extras(help, version, options, doc):
|
||||
if help and any((o.name in ('-h', '--help')) and o.value for o in options):
|
||||
print(doc.strip("\n"))
|
||||
sys.exit()
|
||||
if version and any(o.name == '--version' and o.value for o in options):
|
||||
print(version)
|
||||
sys.exit()
|
||||
|
||||
|
||||
class Dict(dict):
|
||||
def __repr__(self):
|
||||
return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items()))
|
||||
|
||||
|
||||
def docopt(doc, argv=None, help=True, version=None, options_first=False):
|
||||
"""Parse `argv` based on command-line interface described in `doc`.
|
||||
|
||||
`docopt` creates your command-line interface based on its
|
||||
description that you pass as `doc`. Such description can contain
|
||||
--options, <positional-argument>, commands, which could be
|
||||
[optional], (required), (mutually | exclusive) or repeated...
|
||||
|
||||
Parameters
|
||||
----------
|
||||
doc : str
|
||||
Description of your command-line interface.
|
||||
argv : list of str, optional
|
||||
Argument vector to be parsed. sys.argv[1:] is used if not
|
||||
provided.
|
||||
help : bool (default: True)
|
||||
Set to False to disable automatic help on -h or --help
|
||||
options.
|
||||
version : any object
|
||||
If passed, the object will be printed if --version is in
|
||||
`argv`.
|
||||
options_first : bool (default: False)
|
||||
Set to True to require options precede positional arguments,
|
||||
i.e. to forbid options and positional arguments intermix.
|
||||
|
||||
Returns
|
||||
-------
|
||||
args : dict
|
||||
A dictionary, where keys are names of command-line elements
|
||||
such as e.g. "--verbose" and "<path>", and values are the
|
||||
parsed values of those elements.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> from docopt import docopt
|
||||
>>> doc = '''
|
||||
... Usage:
|
||||
... my_program tcp <host> <port> [--timeout=<seconds>]
|
||||
... my_program serial <port> [--baud=<n>] [--timeout=<seconds>]
|
||||
... my_program (-h | --help | --version)
|
||||
...
|
||||
... Options:
|
||||
... -h, --help Show this screen and exit.
|
||||
... --baud=<n> Baudrate [default: 9600]
|
||||
... '''
|
||||
>>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30']
|
||||
>>> docopt(doc, argv)
|
||||
{'--baud': '9600',
|
||||
'--help': False,
|
||||
'--timeout': '30',
|
||||
'--version': False,
|
||||
'<host>': '127.0.0.1',
|
||||
'<port>': '80',
|
||||
'serial': False,
|
||||
'tcp': True}
|
||||
|
||||
See also
|
||||
--------
|
||||
* For video introduction see http://docopt.org
|
||||
* Full documentation is available in README.rst as well as online
|
||||
at https://github.com/docopt/docopt#readme
|
||||
|
||||
"""
|
||||
argv = sys.argv[1:] if argv is None else argv
|
||||
|
||||
usage_sections = parse_section('usage:', doc)
|
||||
if len(usage_sections) == 0:
|
||||
raise DocoptLanguageError('"usage:" (case-insensitive) not found.')
|
||||
if len(usage_sections) > 1:
|
||||
raise DocoptLanguageError('More than one "usage:" (case-insensitive).')
|
||||
DocoptExit.usage = usage_sections[0]
|
||||
|
||||
options = parse_defaults(doc)
|
||||
pattern = parse_pattern(formal_usage(DocoptExit.usage), options)
|
||||
# [default] syntax for argument is disabled
|
||||
#for a in pattern.flat(Argument):
|
||||
# same_name = [d for d in arguments if d.name == a.name]
|
||||
# if same_name:
|
||||
# a.value = same_name[0].value
|
||||
argv = parse_argv(Tokens(argv), list(options), options_first)
|
||||
pattern_options = set(pattern.flat(Option))
|
||||
for options_shortcut in pattern.flat(OptionsShortcut):
|
||||
doc_options = parse_defaults(doc)
|
||||
options_shortcut.children = list(set(doc_options) - pattern_options)
|
||||
#if any_options:
|
||||
# options_shortcut.children += [Option(o.short, o.long, o.argcount)
|
||||
# for o in argv if type(o) is Option]
|
||||
extras(help, version, argv, doc)
|
||||
matched, left, collected = pattern.fix().match(argv)
|
||||
if matched and left == []: # better error message if left?
|
||||
return Dict((a.name, a.value) for a in (pattern.flat() + collected))
|
||||
raise DocoptExit()
|
@ -1,46 +0,0 @@
|
||||
# A file downloader.
|
||||
|
||||
import contextlib, os, tempfile, timer, urllib2, urlparse
|
||||
|
||||
class Downloader:
|
||||
def __init__(self, dir=None):
|
||||
self.dir = dir
|
||||
|
||||
# Downloads a file and removes it when exiting a block.
|
||||
# Usage:
|
||||
# d = Downloader()
|
||||
# with d.download(url) as f:
|
||||
# use_file(f)
|
||||
def download(self, url, cookie=None):
|
||||
suffix = os.path.splitext(urlparse.urlsplit(url)[2])[1]
|
||||
fd, filename = tempfile.mkstemp(suffix=suffix, dir=self.dir)
|
||||
os.close(fd)
|
||||
with timer.print_time('Downloading', url, 'to', filename):
|
||||
opener = urllib2.build_opener()
|
||||
if cookie:
|
||||
opener.addheaders.append(('Cookie', cookie))
|
||||
num_tries = 2
|
||||
for i in range(num_tries):
|
||||
try:
|
||||
f = opener.open(url)
|
||||
except urllib2.URLError, e:
|
||||
print('Failed to open url', url)
|
||||
continue
|
||||
length = f.headers.get('content-length')
|
||||
if not length:
|
||||
print('Failed to get content-length')
|
||||
continue
|
||||
length = int(length)
|
||||
with open(filename, 'wb') as out:
|
||||
count = 0
|
||||
while count < length:
|
||||
data = f.read(1024 * 1024)
|
||||
count += len(data)
|
||||
out.write(data)
|
||||
@contextlib.contextmanager
|
||||
def remove(filename):
|
||||
try:
|
||||
yield filename
|
||||
finally:
|
||||
os.remove(filename)
|
||||
return remove(filename)
|
27
support/fmt.pro
Normal file
27
support/fmt.pro
Normal file
@ -0,0 +1,27 @@
|
||||
# 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
|
273
support/manage.py
Executable file
273
support/manage.py
Executable file
@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Manage site and releases.
|
||||
|
||||
Usage:
|
||||
manage.py release [<branch>]
|
||||
manage.py site
|
||||
|
||||
For the release command $FMT_TOKEN should contain a GitHub personal access token
|
||||
obtained from https://github.com/settings/tokens.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime, docopt, errno, fileinput, json, os
|
||||
import re, requests, shutil, sys, tempfile
|
||||
from contextlib import contextmanager
|
||||
from distutils.version import LooseVersion
|
||||
from subprocess import check_call
|
||||
|
||||
|
||||
class Git:
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
|
||||
def call(self, method, args, **kwargs):
|
||||
return check_call(['git', method] + list(args), **kwargs)
|
||||
|
||||
def add(self, *args):
|
||||
return self.call('add', args, cwd=self.dir)
|
||||
|
||||
def checkout(self, *args):
|
||||
return self.call('checkout', args, cwd=self.dir)
|
||||
|
||||
def clean(self, *args):
|
||||
return self.call('clean', args, cwd=self.dir)
|
||||
|
||||
def clone(self, *args):
|
||||
return self.call('clone', list(args) + [self.dir])
|
||||
|
||||
def commit(self, *args):
|
||||
return self.call('commit', args, cwd=self.dir)
|
||||
|
||||
def pull(self, *args):
|
||||
return self.call('pull', args, cwd=self.dir)
|
||||
|
||||
def push(self, *args):
|
||||
return self.call('push', args, cwd=self.dir)
|
||||
|
||||
def reset(self, *args):
|
||||
return self.call('reset', args, cwd=self.dir)
|
||||
|
||||
def update(self, *args):
|
||||
clone = not os.path.exists(self.dir)
|
||||
if clone:
|
||||
self.clone(*args)
|
||||
return clone
|
||||
|
||||
|
||||
def clean_checkout(repo, branch):
|
||||
repo.clean('-f', '-d')
|
||||
repo.reset('--hard')
|
||||
repo.checkout(branch)
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self, cwd):
|
||||
self.cwd = cwd
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
|
||||
check_call(args, **kwargs)
|
||||
|
||||
|
||||
def create_build_env():
|
||||
"""Create a build environment."""
|
||||
class Env:
|
||||
pass
|
||||
env = Env()
|
||||
|
||||
# Import the documentation build module.
|
||||
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(env.fmt_dir, 'doc'))
|
||||
import build
|
||||
|
||||
env.build_dir = 'build'
|
||||
env.versions = build.versions
|
||||
|
||||
# Virtualenv and repos are cached to speed up builds.
|
||||
build.create_build_env(os.path.join(env.build_dir, 'virtualenv'))
|
||||
|
||||
env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
|
||||
return env
|
||||
|
||||
|
||||
@contextmanager
|
||||
def rewrite(filename):
|
||||
class Buffer:
|
||||
pass
|
||||
buffer = Buffer()
|
||||
if not os.path.exists(filename):
|
||||
buffer.data = ''
|
||||
yield buffer
|
||||
return
|
||||
with open(filename) as f:
|
||||
buffer.data = f.read()
|
||||
yield buffer
|
||||
with open(filename, 'w') as f:
|
||||
f.write(buffer.data)
|
||||
|
||||
|
||||
fmt_repo_url = 'git@github.com:fmtlib/fmt'
|
||||
|
||||
|
||||
def update_site(env):
|
||||
env.fmt_repo.update(fmt_repo_url)
|
||||
|
||||
doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io'))
|
||||
doc_repo.update('git@github.com:fmtlib/fmtlib.github.io')
|
||||
|
||||
for version in env.versions:
|
||||
clean_checkout(env.fmt_repo, version)
|
||||
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
|
||||
# Remove the old theme.
|
||||
for entry in os.listdir(target_doc_dir):
|
||||
path = os.path.join(target_doc_dir, entry)
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
# Copy the new theme.
|
||||
for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap',
|
||||
'conf.py', 'fmt.less']:
|
||||
src = os.path.join(env.fmt_dir, 'doc', entry)
|
||||
dst = os.path.join(target_doc_dir, entry)
|
||||
copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile
|
||||
copy(src, dst)
|
||||
# Rename index to contents.
|
||||
contents = os.path.join(target_doc_dir, 'contents.rst')
|
||||
if not os.path.exists(contents):
|
||||
os.rename(os.path.join(target_doc_dir, 'index.rst'), contents)
|
||||
# Fix issues in reference.rst/api.rst.
|
||||
for filename in ['reference.rst', 'api.rst', 'index.rst']:
|
||||
pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M)
|
||||
with rewrite(os.path.join(target_doc_dir, filename)) as b:
|
||||
b.data = b.data.replace('std::ostream &', 'std::ostream&')
|
||||
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
|
||||
b.data = b.data.replace('std::FILE*', 'std::FILE *')
|
||||
b.data = b.data.replace('unsigned int', 'unsigned')
|
||||
#b.data = b.data.replace('operator""_', 'operator"" _')
|
||||
b.data = b.data.replace(', size_t', ', std::size_t')
|
||||
b.data = b.data.replace('aa long', 'a long')
|
||||
b.data = b.data.replace('serveral', 'several')
|
||||
if version.startswith('6.2.'):
|
||||
b.data = b.data.replace(
|
||||
'vformat(const S&, basic_format_args<' +
|
||||
'buffer_context<Char>>)',
|
||||
'vformat(const S&, basic_format_args<' +
|
||||
'buffer_context<type_identity_t<Char>>>)')
|
||||
# Fix a broken link in index.rst.
|
||||
index = os.path.join(target_doc_dir, 'index.rst')
|
||||
with rewrite(index) as b:
|
||||
b.data = b.data.replace(
|
||||
'doc/latest/index.html#format-string-syntax', 'syntax.html')
|
||||
# Build the docs.
|
||||
html_dir = os.path.join(env.build_dir, 'html')
|
||||
if os.path.exists(html_dir):
|
||||
shutil.rmtree(html_dir)
|
||||
include_dir = env.fmt_repo.dir
|
||||
if LooseVersion(version) >= LooseVersion('5.0.0'):
|
||||
include_dir = os.path.join(include_dir, 'include', 'fmt')
|
||||
elif LooseVersion(version) >= LooseVersion('3.0.0'):
|
||||
include_dir = os.path.join(include_dir, 'fmt')
|
||||
import build
|
||||
build.build_docs(version, doc_dir=target_doc_dir,
|
||||
include_dir=include_dir, work_dir=env.build_dir)
|
||||
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
|
||||
# Create symlinks for older versions.
|
||||
for link, target in {'index': 'contents', 'api': 'reference'}.items():
|
||||
link = os.path.join(html_dir, link) + '.html'
|
||||
target += '.html'
|
||||
if os.path.exists(os.path.join(html_dir, target)) and \
|
||||
not os.path.exists(link):
|
||||
os.symlink(target, link)
|
||||
# Copy docs to the website.
|
||||
version_doc_dir = os.path.join(doc_repo.dir, version)
|
||||
try:
|
||||
shutil.rmtree(version_doc_dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
shutil.move(html_dir, version_doc_dir)
|
||||
|
||||
|
||||
def release(args):
|
||||
env = create_build_env()
|
||||
fmt_repo = env.fmt_repo
|
||||
|
||||
branch = args.get('<branch>')
|
||||
if branch is None:
|
||||
branch = 'master'
|
||||
if not fmt_repo.update('-b', branch, fmt_repo_url):
|
||||
clean_checkout(fmt_repo, branch)
|
||||
|
||||
# Convert changelog from RST to GitHub-flavored Markdown and get the
|
||||
# version.
|
||||
changelog = 'ChangeLog.rst'
|
||||
changelog_path = os.path.join(fmt_repo.dir, changelog)
|
||||
import rst2md
|
||||
changes, version = rst2md.convert(changelog_path)
|
||||
cmakelists = 'CMakeLists.txt'
|
||||
for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists),
|
||||
inplace=True):
|
||||
prefix = 'set(FMT_VERSION '
|
||||
if line.startswith(prefix):
|
||||
line = prefix + version + ')\n'
|
||||
sys.stdout.write(line)
|
||||
|
||||
# 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'):
|
||||
line = version + ' - ' + datetime.date.today().isoformat()
|
||||
title_len = len(line)
|
||||
line += '\n'
|
||||
elif title_len:
|
||||
line = '-' * title_len + '\n'
|
||||
title_len = 0
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Add the version to the build script.
|
||||
script = os.path.join('doc', 'build.py')
|
||||
script_path = os.path.join(fmt_repo.dir, script)
|
||||
for line in fileinput.input(script_path, inplace=True):
|
||||
m = re.match(r'( *versions = )\[(.+)\]', line)
|
||||
if m:
|
||||
line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version)
|
||||
sys.stdout.write(line)
|
||||
|
||||
fmt_repo.checkout('-B', 'release')
|
||||
fmt_repo.add(changelog, cmakelists, script)
|
||||
fmt_repo.commit('-m', 'Update version')
|
||||
|
||||
# Build the docs and package.
|
||||
run = Runner(fmt_repo.dir)
|
||||
run('cmake', '.')
|
||||
run('make', 'doc', 'package_source')
|
||||
update_site(env)
|
||||
|
||||
# Create a release on GitHub.
|
||||
fmt_repo.push('origin', 'release')
|
||||
params = {'access_token': os.getenv('FMT_TOKEN')}
|
||||
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
|
||||
params=params,
|
||||
data=json.dumps({'tag_name': version,
|
||||
'target_commitish': 'release',
|
||||
'body': changes, 'draft': True}))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to create a release ' + str(r))
|
||||
id = r.json()['id']
|
||||
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
|
||||
package = 'fmt-{}.zip'.format(version)
|
||||
r = requests.post(
|
||||
'{}/{}/assets?name={}'.format(uploads_url, id, package),
|
||||
headers={'Content-Type': 'application/zip'},
|
||||
params=params, data=open('build/fmt/' + package, 'rb'))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to upload an asset ' + str(r))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt.docopt(__doc__)
|
||||
if args.get('release'):
|
||||
release(args)
|
||||
elif args.get('site'):
|
||||
update_site(create_build_env())
|
@ -1,183 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Release script
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime, fileinput, json, os, re, requests, shutil, sys, tempfile
|
||||
from docutils import nodes, writers, core
|
||||
from subprocess import check_call
|
||||
|
||||
class MDWriter(writers.Writer):
|
||||
"""GitHub-flavored markdown writer"""
|
||||
|
||||
supported = ('md',)
|
||||
"""Formats this writer supports."""
|
||||
|
||||
def translate(self):
|
||||
translator = Translator(self.document)
|
||||
self.document.walkabout(translator)
|
||||
self.output = (translator.output, translator.version)
|
||||
|
||||
|
||||
def is_github_ref(node):
|
||||
return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
|
||||
|
||||
|
||||
class Translator(nodes.NodeVisitor):
|
||||
def __init__(self, document):
|
||||
nodes.NodeVisitor.__init__(self, document)
|
||||
self.output = ''
|
||||
self.indent = 0
|
||||
self.preserve_newlines = False
|
||||
|
||||
def write(self, text):
|
||||
self.output += text.replace('\n', '\n' + ' ' * self.indent)
|
||||
|
||||
def visit_document(self, node):
|
||||
pass
|
||||
|
||||
def depart_document(self, node):
|
||||
pass
|
||||
|
||||
def visit_section(self, node):
|
||||
pass
|
||||
|
||||
def depart_section(self, node):
|
||||
# Skip all sections except the first one.
|
||||
raise nodes.StopTraversal
|
||||
|
||||
def visit_title(self, node):
|
||||
self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def depart_title(self, node):
|
||||
pass
|
||||
|
||||
def visit_Text(self, node):
|
||||
if not self.preserve_newlines:
|
||||
node = node.replace('\n', ' ')
|
||||
self.write(node)
|
||||
|
||||
def depart_Text(self, node):
|
||||
pass
|
||||
|
||||
def visit_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def depart_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def visit_list_item(self, node):
|
||||
self.write('* ')
|
||||
self.indent += 2
|
||||
|
||||
def depart_list_item(self, node):
|
||||
self.indent -= 2
|
||||
self.write('\n\n')
|
||||
|
||||
def visit_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def depart_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def visit_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('[')
|
||||
|
||||
def depart_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('](' + node['refuri'] + ')')
|
||||
|
||||
def visit_target(self, node):
|
||||
pass
|
||||
|
||||
def depart_target(self, node):
|
||||
pass
|
||||
|
||||
def visit_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def depart_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
self.write('\n\n```')
|
||||
if 'c++' in node['classes']:
|
||||
self.write('c++')
|
||||
self.write('\n')
|
||||
self.preserve_newlines = True
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
self.write('\n```\n')
|
||||
self.preserve_newlines = False
|
||||
|
||||
def visit_inline(self, node):
|
||||
pass
|
||||
|
||||
def depart_inline(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self):
|
||||
self.cwd = '.'
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
|
||||
check_call(args, **kwargs)
|
||||
|
||||
workdir = tempfile.mkdtemp()
|
||||
try:
|
||||
run = Runner()
|
||||
cppformat_dir = os.path.join(workdir, 'cppformat')
|
||||
run('git', 'clone', 'git@github.com:cppformat/cppformat.git', cppformat_dir)
|
||||
|
||||
# Convert changelog from RST to GitHub-flavored Markdown and get the version.
|
||||
changelog = 'ChangeLog.rst'
|
||||
changelog_path = os.path.join(cppformat_dir, changelog)
|
||||
changes, version = core.publish_file(source_path=changelog_path, writer=MDWriter())
|
||||
cmakelists = 'CMakeLists.txt'
|
||||
for line in fileinput.input(os.path.join(cppformat_dir, cmakelists), inplace=True):
|
||||
prefix = 'set(CPPFORMAT_VERSION '
|
||||
if line.startswith(prefix):
|
||||
line = prefix + version + ')\n'
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Update the version in the changelog.
|
||||
title_len = 0
|
||||
for line in fileinput.input(changelog_path, inplace=True):
|
||||
if line.startswith(version + ' - TBD'):
|
||||
line = version + ' - ' + datetime.date.today().isoformat()
|
||||
title_len = len(line)
|
||||
line += '\n'
|
||||
elif title_len:
|
||||
line = '-' * title_len + '\n'
|
||||
title_len = 0
|
||||
sys.stdout.write(line)
|
||||
run.cwd = cppformat_dir
|
||||
run('git', 'checkout', '-b', 'release')
|
||||
run('git', 'add', changelog, cmakelists)
|
||||
run('git', 'commit', '-m', 'Update version')
|
||||
|
||||
# Build the docs and package.
|
||||
run('cmake', '.')
|
||||
run('make', 'doc', 'package_source')
|
||||
site_dir = os.path.join(workdir, 'cppformat.github.io')
|
||||
run('git', 'clone', 'git@github.com:cppformat/cppformat.github.io.git', site_dir)
|
||||
doc_dir = os.path.join(site_dir, version)
|
||||
shutil.copytree(os.path.join(cppformat_dir, 'doc', 'html'), doc_dir,
|
||||
ignore=shutil.ignore_patterns('.doctrees', '.buildinfo'))
|
||||
run.cwd = site_dir
|
||||
run('git', 'add', doc_dir)
|
||||
run('git', 'commit', '-m', 'Update docs')
|
||||
|
||||
# Create a release on GitHub.
|
||||
run('git', 'push', 'origin', 'release', cwd=cppformat_dir)
|
||||
r = requests.post('https://api.github.com/repos/cppformat/cppformat/releases',
|
||||
params={'access_token': os.getenv('CPPFORMAT_TOKEN')},
|
||||
data=json.dumps({'tag_name1': version, 'target_commitish': 'release',
|
||||
'body': changes, 'draft': True}))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to create a release ' + str(r))
|
||||
finally:
|
||||
shutil.rmtree(workdir)
|
159
support/rst2md.py
Executable file
159
support/rst2md.py
Executable file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python
|
||||
# reStructuredText (RST) to GitHub-flavored Markdown converter
|
||||
|
||||
import re, sys
|
||||
from docutils import core, nodes, writers
|
||||
|
||||
|
||||
def is_github_ref(node):
|
||||
return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
|
||||
|
||||
|
||||
class Translator(nodes.NodeVisitor):
|
||||
def __init__(self, document):
|
||||
nodes.NodeVisitor.__init__(self, document)
|
||||
self.output = ''
|
||||
self.indent = 0
|
||||
self.preserve_newlines = False
|
||||
|
||||
def write(self, text):
|
||||
self.output += text.replace('\n', '\n' + ' ' * self.indent)
|
||||
|
||||
def visit_document(self, node):
|
||||
pass
|
||||
|
||||
def depart_document(self, node):
|
||||
pass
|
||||
|
||||
def visit_section(self, node):
|
||||
pass
|
||||
|
||||
def depart_section(self, node):
|
||||
# Skip all sections except the first one.
|
||||
raise nodes.StopTraversal
|
||||
|
||||
def visit_title(self, node):
|
||||
self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def visit_title_reference(self, node):
|
||||
raise Exception(node)
|
||||
|
||||
def depart_title(self, node):
|
||||
pass
|
||||
|
||||
def visit_Text(self, node):
|
||||
if not self.preserve_newlines:
|
||||
node = node.replace('\n', ' ')
|
||||
self.write(node)
|
||||
|
||||
def depart_Text(self, node):
|
||||
pass
|
||||
|
||||
def visit_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def depart_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def visit_list_item(self, node):
|
||||
self.write('* ')
|
||||
self.indent += 2
|
||||
|
||||
def depart_list_item(self, node):
|
||||
self.indent -= 2
|
||||
self.write('\n\n')
|
||||
|
||||
def visit_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def depart_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def visit_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('[')
|
||||
|
||||
def depart_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('](' + node['refuri'] + ')')
|
||||
|
||||
def visit_target(self, node):
|
||||
pass
|
||||
|
||||
def depart_target(self, node):
|
||||
pass
|
||||
|
||||
def visit_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def depart_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
self.write('\n\n```')
|
||||
if 'c++' in node['classes']:
|
||||
self.write('c++')
|
||||
self.write('\n')
|
||||
self.preserve_newlines = True
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
self.write('\n```\n')
|
||||
self.preserve_newlines = False
|
||||
|
||||
def visit_inline(self, node):
|
||||
pass
|
||||
|
||||
def depart_inline(self, node):
|
||||
pass
|
||||
|
||||
def visit_image(self, node):
|
||||
self.write('')
|
||||
|
||||
def depart_image(self, node):
|
||||
pass
|
||||
|
||||
def write_row(self, row, widths):
|
||||
for i, entry in enumerate(row):
|
||||
text = entry[0][0] if len(entry) > 0 else ''
|
||||
if i != 0:
|
||||
self.write('|')
|
||||
self.write('{:{}}'.format(text, widths[i]))
|
||||
self.write('\n')
|
||||
|
||||
def visit_table(self, node):
|
||||
table = node.children[0]
|
||||
colspecs = table[:-2]
|
||||
thead = table[-2]
|
||||
tbody = table[-1]
|
||||
widths = [int(cs['colwidth']) for cs in colspecs]
|
||||
sep = '|'.join(['-' * w for w in widths]) + '\n'
|
||||
self.write('\n\n')
|
||||
self.write_row(thead[0], widths)
|
||||
self.write(sep)
|
||||
for row in tbody:
|
||||
self.write_row(row, widths)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def depart_table(self, node):
|
||||
pass
|
||||
|
||||
class MDWriter(writers.Writer):
|
||||
"""GitHub-flavored markdown writer"""
|
||||
|
||||
supported = ('md',)
|
||||
"""Formats this writer supports."""
|
||||
|
||||
def translate(self):
|
||||
translator = Translator(self.document)
|
||||
self.document.walkabout(translator)
|
||||
self.output = (translator.output, translator.version)
|
||||
|
||||
|
||||
def convert(rst_path):
|
||||
"""Converts RST file to Markdown."""
|
||||
return core.publish_file(source_path=rst_path, writer=MDWriter())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
convert(sys.argv[1])
|
@ -1,2 +1,2 @@
|
||||
If you are not redirected automatically, follow the
|
||||
`link to the C++ Format documentation <http://cppformat.github.io/latest/>`_.
|
||||
`link to the fmt documentation <https://fmt.dev/latest/>`_.
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
{% block extrahead %}
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="1;url=http://cppformat.github.io/latest/">
|
||||
<meta http-equiv="refresh" content="1;url=https://fmt.dev/latest/">
|
||||
<script type="text/javascript">
|
||||
window.location.href = "http://cppformat.github.io/latest/"
|
||||
window.location.href = "https://fmt.dev/latest/"
|
||||
</script>
|
||||
<title>Page Redirection</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block document %}
|
||||
If you are not redirected automatically, follow the <a href='http://cppformat.github.io/latest/'>link to the C++ Format documentation</a>.
|
||||
If you are not redirected automatically, follow the <a href='https://fmt.dev/latest/'>link to the fmt documentation</a>.
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
|
@ -1,35 +0,0 @@
|
||||
# A with statement based timer.
|
||||
|
||||
from __future__ import print_function
|
||||
from contextlib import contextmanager
|
||||
import timeit
|
||||
|
||||
class Timer:
|
||||
"""
|
||||
A with statement based timer.
|
||||
Usage:
|
||||
t = Timer()
|
||||
with t:
|
||||
do_something()
|
||||
time = t.time
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
self.start = timeit.default_timer()
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
finish = timeit.default_timer()
|
||||
self.time = finish - self.start
|
||||
|
||||
@contextmanager
|
||||
def print_time(*args):
|
||||
"""
|
||||
Measures and prints the time taken to execute nested code.
|
||||
args: Additional arguments to print.
|
||||
"""
|
||||
t = Timer()
|
||||
print(*args)
|
||||
with t:
|
||||
yield
|
||||
print(*args, end=' ')
|
||||
print('finished in {0:.2f} second(s)'.format(t.time))
|
@ -2,76 +2,118 @@
|
||||
# Build the project on Travis CI.
|
||||
|
||||
from __future__ import print_function
|
||||
import errno, os, re, shutil, sys, tempfile, urllib
|
||||
from subprocess import call, check_call, check_output, Popen, PIPE, STDOUT
|
||||
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
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
|
||||
build = os.environ['BUILD']
|
||||
if build == 'Doc':
|
||||
travis = 'TRAVIS' in os.environ
|
||||
# Install dependencies.
|
||||
if travis:
|
||||
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)
|
||||
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(['npm', 'install', '-g', 'less', 'less-plugin-clean-css'])
|
||||
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])
|
||||
cppformat_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
sys.path.insert(0, os.path.join(cppformat_dir, 'doc'))
|
||||
import build
|
||||
html_dir = build.build_docs()
|
||||
repo = 'cppformat.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 cppformat.github.io repo.
|
||||
rmtree_if_exists(repo)
|
||||
git_url = 'https://github.com/' if travis else 'git@github.com:'
|
||||
check_call(['git', 'clone', git_url + 'cppformat/{}.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/cppformat/cppformat.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 CalledProcessError(p.returncode, cmd)
|
||||
exit(0)
|
||||
|
||||
check_call(['git', 'submodule', 'update', '--init'])
|
||||
check_call(['cmake', '-DCMAKE_BUILD_TYPE=' + build, '-DFMT_PEDANTIC=ON', '.'])
|
||||
check_call(['make', '-j4'])
|
||||
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):
|
||||
with open('Testing/Temporary/LastTest.log', 'r') as f:
|
||||
print(f.read())
|
||||
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:cppformat/cppformat.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)
|
30
support/update-coverity-branch.py
Executable file
30
support/update-coverity-branch.py
Executable file
@ -0,0 +1,30 @@
|
||||
#!/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,15 +1,15 @@
|
||||
set(FMT_GMOCK_DIR ../gmock)
|
||||
|
||||
include_directories(.. ${FMT_GMOCK_DIR})
|
||||
#------------------------------------------------------------------------------
|
||||
# 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
|
||||
${FMT_GMOCK_DIR}/gmock-gtest-all.cc ${FMT_GMOCK_DIR}/gmock/gmock.h
|
||||
${FMT_GMOCK_DIR}/gtest/gtest.h ${FMT_GMOCK_DIR}/gtest/gtest-spi.h)
|
||||
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})
|
||||
@ -17,25 +17,18 @@ else ()
|
||||
target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0)
|
||||
endif ()
|
||||
|
||||
# Check if variadic templates are working and not affected by GCC bug 39653:
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39653
|
||||
check_cxx_source_compiles("
|
||||
template <class T, class ...Types>
|
||||
struct S { typedef typename S<Types...>::type type; };
|
||||
int main() {}" FMT_VARIADIC_TEMPLATES)
|
||||
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
|
||||
|
||||
# Check if initializer lists are supported.
|
||||
check_cxx_source_compiles("
|
||||
#include <initializer_list>
|
||||
int main() {}" FMT_INITIALIZER_LIST)
|
||||
|
||||
if (NOT FMT_VARIADIC_TEMPLATES OR NOT FMT_INITIALIZER_LIST)
|
||||
add_definitions(-DGTEST_LANG_CXX11=0)
|
||||
endif ()
|
||||
|
||||
# Workaround a bug in implementation of variadic templates in MSVC11.
|
||||
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.
|
||||
@ -43,113 +36,201 @@ 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
|
||||
|
||||
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
||||
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
||||
target_link_libraries(test-main cppformat gmock)
|
||||
target_include_directories(test-main SYSTEM PUBLIC gtest gmock)
|
||||
target_link_libraries(test-main gmock fmt)
|
||||
|
||||
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 ()
|
||||
endfunction()
|
||||
|
||||
# Adds a test.
|
||||
# Usage: add_fmt_test(name [CUSTOM_LINK] srcs...)
|
||||
# Usage: add_fmt_test(name srcs...)
|
||||
function(add_fmt_test name)
|
||||
cmake_parse_arguments(add_fmt_test CUSTOM_LINK "" "" ${ARGN})
|
||||
add_executable(${name} ${name}.cc ${add_fmt_test_UNPARSED_ARGUMENTS})
|
||||
add_fmt_executable(${name} ${name}.cc ${ARGN})
|
||||
target_link_libraries(${name} test-main)
|
||||
if (NOT add_fmt_test_CUSTOM_LINK)
|
||||
target_link_libraries(${name} cppformat)
|
||||
|
||||
# Define if certain C++ features can be used.
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
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(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)
|
||||
if (FMT_PEDANTIC AND MSVC)
|
||||
set_target_properties(format-test PROPERTIES COMPILE_FLAGS /W4)
|
||||
add_fmt_test(format-test mock-allocator.h)
|
||||
if (MSVC)
|
||||
target_compile_options(format-test PRIVATE /bigobj)
|
||||
endif ()
|
||||
|
||||
add_fmt_test(format-impl-test CUSTOM_LINK)
|
||||
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||
add_fmt_test(format-impl-test)
|
||||
endif ()
|
||||
add_fmt_test(locale-test)
|
||||
add_fmt_test(ostream-test)
|
||||
add_fmt_test(compile-test)
|
||||
add_fmt_test(printf-test)
|
||||
foreach (target format-test printf-test)
|
||||
if (FMT_PEDANTIC AND CMAKE_COMPILER_IS_GNUCXX)
|
||||
set_target_properties(${target} PROPERTIES COMPILE_FLAGS
|
||||
"-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros")
|
||||
endif ()
|
||||
if (CPP11_FLAG)
|
||||
set_target_properties(${target} PROPERTIES COMPILE_FLAGS ${CPP11_FLAG})
|
||||
endif ()
|
||||
endforeach ()
|
||||
add_fmt_test(util-test mock-allocator.h)
|
||||
if (CPP11_FLAG)
|
||||
set_target_properties(util-test PROPERTIES COMPILE_FLAGS ${CPP11_FLAG})
|
||||
endif ()
|
||||
add_fmt_test(custom-formatter-test)
|
||||
add_fmt_test(ranges-test)
|
||||
add_fmt_test(scan-test)
|
||||
|
||||
check_cxx_source_compiles("
|
||||
enum C : char {A};
|
||||
int main() {}"
|
||||
HAVE_ENUM_BASE)
|
||||
if (HAVE_ENUM_BASE)
|
||||
set_target_properties(util-test
|
||||
PROPERTIES COMPILE_DEFINITIONS "FMT_USE_ENUM_BASE=1")
|
||||
endif ()
|
||||
|
||||
foreach (src ${FMT_SOURCES})
|
||||
set(FMT_TEST_SOURCES ${FMT_TEST_SOURCES} ../${src})
|
||||
endforeach ()
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <type_traits>
|
||||
class C { void operator=(const C&); };
|
||||
int main() { static_assert(!std::is_copy_assignable<C>::value, \"\"); }"
|
||||
HAVE_TYPE_TRAITS)
|
||||
if (HAVE_TYPE_TRAITS)
|
||||
foreach (target format-test util-test)
|
||||
set_target_properties(${target}
|
||||
PROPERTIES COMPILE_DEFINITIONS "FMT_USE_TYPE_TRAITS=1")
|
||||
endforeach ()
|
||||
endif ()
|
||||
|
||||
add_executable(macro-test macro-test.cc ${FMT_TEST_SOURCES} ${TEST_MAIN_SRC})
|
||||
target_link_libraries(macro-test gmock)
|
||||
|
||||
if (HAVE_OPEN)
|
||||
add_executable(posix-mock-test posix-mock-test.cc ../format.cc ${TEST_MAIN_SRC})
|
||||
if (NOT MSVC_BUILD_STATIC)
|
||||
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)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (HAVE_STRTOD_L)
|
||||
target_compile_definitions(posix-mock-test PRIVATE FMT_LOCALE)
|
||||
endif ()
|
||||
add_test(NAME posix-mock-test COMMAND posix-mock-test)
|
||||
add_fmt_test(posix-test)
|
||||
add_fmt_test(os-test)
|
||||
endif ()
|
||||
|
||||
add_executable(header-only-test
|
||||
add_fmt_executable(header-only-test
|
||||
header-only-test.cc header-only-test2.cc test-main.cc)
|
||||
set_target_properties(header-only-test
|
||||
PROPERTIES COMPILE_DEFINITIONS "FMT_HEADER_ONLY=1")
|
||||
target_link_libraries(header-only-test gmock)
|
||||
|
||||
# Test that the library can be compiled with exceptions disabled.
|
||||
check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG)
|
||||
if (HAVE_FNO_EXCEPTIONS_FLAG)
|
||||
add_library(noexception-test STATIC ../format.cc)
|
||||
set_target_properties(noexception-test
|
||||
PROPERTIES COMPILE_FLAGS -fno-exceptions)
|
||||
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 ()
|
||||
|
||||
# Test compilation with default flags.
|
||||
if (FMT_TEST_DEFAULT_FLAGS)
|
||||
file(GLOB src RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cc *.h)
|
||||
foreach (s ${FMT_SOURCES})
|
||||
set(src ${src} ../${s})
|
||||
endforeach ()
|
||||
add_library(testformat STATIC ${src})
|
||||
endif ()
|
||||
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
|
||||
|
||||
if (FMT_PEDANTIC)
|
||||
add_test(compile-test ${CMAKE_CTEST_COMMAND}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/compile-test"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/compile-test"
|
||||
--build-generator ${CMAKE_GENERATOR}
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM})
|
||||
# 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 compiles without windows.h.
|
||||
add_library(no-windows-h-test ../format.cc)
|
||||
set_target_properties(no-windows-h-test
|
||||
PROPERTIES COMPILE_DEFINITIONS "FMT_USE_WINDOWS_H=0")
|
||||
# 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)
|
||||
target_include_directories(
|
||||
noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(noexception-test PRIVATE -fno-exceptions)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(noexception-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Test that the library compiles without locale.
|
||||
add_library(nolocale-test ../src/format.cc)
|
||||
target_include_directories(
|
||||
nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(
|
||||
nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1)
|
||||
|
||||
add_test(compile-error-test ${CMAKE_CTEST_COMMAND}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/compile-error-test"
|
||||
--build-generator ${CMAKE_GENERATOR}
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}"
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
"-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}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/find-package-test"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/find-package-test"
|
||||
--build-generator ${CMAKE_GENERATOR}
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DFMT_DIR=${PROJECT_BINARY_DIR}"
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||
|
||||
# Test if the targets are found when add_subdirectory is used.
|
||||
add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND}
|
||||
-C ${CMAKE_BUILD_TYPE}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test"
|
||||
--build-generator ${CMAKE_GENERATOR}
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
"-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)
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
||||
find_package(CUDA 9.0)
|
||||
else ()
|
||||
include(CheckLanguage)
|
||||
check_language(CUDA)
|
||||
if (CMAKE_CUDA_COMPILER)
|
||||
enable_language(CUDA OPTIONAL)
|
||||
set(CUDA_FOUND TRUE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (CUDA_FOUND)
|
||||
add_subdirectory(cuda-test)
|
||||
add_test(NAME cuda-test COMMAND fmt-in-cuda-test)
|
||||
endif ()
|
||||
endif ()
|
||||
|
17
test/add-subdirectory-test/CMakeLists.txt
Normal file
17
test/add-subdirectory-test/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
project(fmt-test)
|
||||
|
||||
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})
|
||||
target_include_directories(library-test PUBLIC SYSTEM .)
|
||||
|
||||
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})
|
||||
target_include_directories(header-only-test PUBLIC SYSTEM .)
|
||||
endif ()
|
6
test/add-subdirectory-test/main.cc
Normal file
6
test/add-subdirectory-test/main.cc
Normal file
@ -0,0 +1,6 @@
|
||||
#include "fmt/format.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
for(int i = 0; i < argc; ++i)
|
||||
fmt::print("{}: {}\n", i, argv[i]);
|
||||
}
|
@ -1,41 +1,28 @@
|
||||
/*
|
||||
Assertion tests
|
||||
// Formatting library for C++ - assertion tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
Copyright (c) 2015, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY 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.
|
||||
*/
|
||||
|
||||
#include "format.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
EXPECT_DEBUG_DEATH(statement, regex)
|
||||
#else
|
||||
# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, )
|
||||
#endif
|
||||
#include "fmt/core.h"
|
||||
#include "gtest.h"
|
||||
|
||||
TEST(AssertTest, Fail) {
|
||||
EXPECT_DEBUG_DEATH_IF_SUPPORTED(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||
#else
|
||||
fmt::print("warning: death tests are not supported\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool test_condition = false;
|
||||
|
||||
TEST(AssertTest, DanglingElse) {
|
||||
bool executed_else = false;
|
||||
if (test_condition)
|
||||
FMT_ASSERT(true, "");
|
||||
else
|
||||
executed_else = true;
|
||||
EXPECT_TRUE(executed_else);
|
||||
}
|
||||
|
387
test/chrono-test.cc
Normal file
387
test/chrono-test.cc
Normal file
@ -0,0 +1,387 @@
|
||||
// Formatting library for C++ - time formatting tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifdef WIN32
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/chrono.h"
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include "gtest-extra.h"
|
||||
|
||||
std::tm make_tm() {
|
||||
auto time = std::tm();
|
||||
time.tm_mday = 1;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm make_hour(int h) {
|
||||
auto time = make_tm();
|
||||
time.tm_hour = h;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm make_minute(int m) {
|
||||
auto time = make_tm();
|
||||
time.tm_min = m;
|
||||
return time;
|
||||
}
|
||||
|
||||
std::tm make_second(int s) {
|
||||
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) {
|
||||
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));
|
||||
return os.str();
|
||||
}
|
||||
|
||||
TEST(TimeTest, Format) {
|
||||
std::tm 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));
|
||||
}
|
||||
|
||||
TEST(TimeTest, GrowBuffer) {
|
||||
std::string s = "{:";
|
||||
for (int i = 0; i < 30; ++i) s += "%c";
|
||||
s += "}\n";
|
||||
std::time_t t = std::time(nullptr);
|
||||
fmt::format(s, *std::localtime(&t));
|
||||
}
|
||||
|
||||
TEST(TimeTest, FormatToEmptyContainer) {
|
||||
std::string s;
|
||||
auto time = std::tm();
|
||||
time.tm_sec = 42;
|
||||
fmt::format_to(std::back_inserter(s), "{:%S}", time);
|
||||
EXPECT_EQ(s, "42");
|
||||
}
|
||||
|
||||
TEST(TimeTest, EmptyResult) { EXPECT_EQ("", fmt::format("{}", std::tm())); }
|
||||
|
||||
static bool EqualTime(const std::tm& lhs, const std::tm& rhs) {
|
||||
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
|
||||
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
|
||||
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
|
||||
lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday &&
|
||||
lhs.tm_isdst == rhs.tm_isdst;
|
||||
}
|
||||
|
||||
TEST(TimeTest, LocalTime) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(EqualTime(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)));
|
||||
}
|
||||
|
||||
#define EXPECT_TIME(spec, time, duration) \
|
||||
{ \
|
||||
std::locale loc("ja_JP.utf8"); \
|
||||
EXPECT_EQ(format_tm(time, spec, loc), \
|
||||
fmt::format(loc, "{:" spec "}", duration)); \
|
||||
}
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
TEST(ChronoTest, FormatDefault) {
|
||||
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ("42as",
|
||||
fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
|
||||
EXPECT_EQ("42fs",
|
||||
fmt::format("{}", std::chrono::duration<int, std::femto>(42)));
|
||||
EXPECT_EQ("42ps",
|
||||
fmt::format("{}", std::chrono::duration<int, std::pico>(42)));
|
||||
EXPECT_EQ("42ns", fmt::format("{}", std::chrono::nanoseconds(42)));
|
||||
EXPECT_EQ("42µs", fmt::format("{}", std::chrono::microseconds(42)));
|
||||
EXPECT_EQ("42ms", fmt::format("{}", std::chrono::milliseconds(42)));
|
||||
EXPECT_EQ("42cs",
|
||||
fmt::format("{}", std::chrono::duration<int, std::centi>(42)));
|
||||
EXPECT_EQ("42ds",
|
||||
fmt::format("{}", std::chrono::duration<int, std::deci>(42)));
|
||||
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ("42das",
|
||||
fmt::format("{}", std::chrono::duration<int, std::deca>(42)));
|
||||
EXPECT_EQ("42hs",
|
||||
fmt::format("{}", std::chrono::duration<int, std::hecto>(42)));
|
||||
EXPECT_EQ("42ks",
|
||||
fmt::format("{}", std::chrono::duration<int, std::kilo>(42)));
|
||||
EXPECT_EQ("42Ms",
|
||||
fmt::format("{}", std::chrono::duration<int, std::mega>(42)));
|
||||
EXPECT_EQ("42Gs",
|
||||
fmt::format("{}", std::chrono::duration<int, std::giga>(42)));
|
||||
EXPECT_EQ("42Ts",
|
||||
fmt::format("{}", std::chrono::duration<int, std::tera>(42)));
|
||||
EXPECT_EQ("42Ps",
|
||||
fmt::format("{}", std::chrono::duration<int, std::peta>(42)));
|
||||
EXPECT_EQ("42Es",
|
||||
fmt::format("{}", std::chrono::duration<int, std::exa>(42)));
|
||||
EXPECT_EQ("42m", fmt::format("{}", std::chrono::minutes(42)));
|
||||
EXPECT_EQ("42h", fmt::format("{}", std::chrono::hours(42)));
|
||||
EXPECT_EQ(
|
||||
"42[15]s",
|
||||
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
|
||||
EXPECT_EQ(
|
||||
"42[15/4]s",
|
||||
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) {
|
||||
auto s = std::chrono::seconds(42);
|
||||
EXPECT_EQ("42s ", fmt::format("{:5}", s));
|
||||
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
|
||||
EXPECT_EQ(" 42s", fmt::format("{:>5}", s));
|
||||
EXPECT_EQ("**42s**", fmt::format("{:*^7}", s));
|
||||
EXPECT_EQ("03:25:45 ",
|
||||
fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ(" 03:25:45",
|
||||
fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ("~~03:25:45~~",
|
||||
fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ("03:25:45 ",
|
||||
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatSpecs) {
|
||||
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)));
|
||||
EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0)));
|
||||
EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60)));
|
||||
EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234)));
|
||||
EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0)));
|
||||
EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60)));
|
||||
EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42)));
|
||||
EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61)));
|
||||
EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0)));
|
||||
EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24)));
|
||||
EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14)));
|
||||
EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61)));
|
||||
EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0)));
|
||||
EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12)));
|
||||
EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24)));
|
||||
EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4)));
|
||||
EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14)));
|
||||
EXPECT_EQ("03:25:45",
|
||||
fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ("12345", fmt::format("{:%Q}", std::chrono::seconds(12345)));
|
||||
EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, InvalidSpecs) {
|
||||
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,
|
||||
"invalid format");
|
||||
EXPECT_THROW_MSG(fmt::format("{:%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;
|
||||
}
|
||||
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));
|
||||
EXPECT_TIME("%OS", make_second(42), std::chrono::seconds(42));
|
||||
auto time = make_tm();
|
||||
time.tm_hour = 3;
|
||||
time.tm_min = 25;
|
||||
time.tm_sec = 45;
|
||||
auto sec = std::chrono::seconds(12345);
|
||||
EXPECT_TIME("%r", time, sec);
|
||||
EXPECT_TIME("%p", time, sec);
|
||||
}
|
||||
|
||||
typedef std::chrono::duration<double, std::milli> dms;
|
||||
|
||||
TEST(ChronoTest, FormatDefaultFP) {
|
||||
typedef std::chrono::duration<float> fs;
|
||||
EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
|
||||
typedef std::chrono::duration<float, std::milli> fms;
|
||||
EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234)));
|
||||
typedef std::chrono::duration<double> ds;
|
||||
EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234)));
|
||||
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");
|
||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatFullSpecs) {
|
||||
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)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatSimpleQq) {
|
||||
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;
|
||||
EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234)));
|
||||
typedef std::chrono::duration<double> ds;
|
||||
EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", ds(1.234)));
|
||||
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");
|
||||
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) {
|
||||
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)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, InvalidWidthId) {
|
||||
EXPECT_THROW(fmt::format("{:{o}", std::chrono::seconds(0)),
|
||||
fmt::format_error);
|
||||
}
|
||||
|
||||
TEST(ChronoTest, InvalidColons) {
|
||||
EXPECT_THROW(fmt::format("{0}=:{0::", std::chrono::seconds(0)),
|
||||
fmt::format_error);
|
||||
}
|
||||
|
||||
TEST(ChronoTest, NegativeDurations) {
|
||||
EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
|
||||
EXPECT_EQ("-03:25:45",
|
||||
fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
|
||||
EXPECT_EQ("-00:01",
|
||||
fmt::format("{:%M:%S}", std::chrono::duration<double>(-1)));
|
||||
EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(-12345)));
|
||||
EXPECT_EQ("-00.127",
|
||||
fmt::format("{:%S}",
|
||||
std::chrono::duration<signed char, std::milli>{-127}));
|
||||
auto min = std::numeric_limits<int>::min();
|
||||
EXPECT_EQ(fmt::format("{}", min),
|
||||
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));
|
||||
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)),
|
||||
"1as");
|
||||
EXPECT_EQ(fmt::format("{:%R}", std::chrono::duration<char, std::mega>{2}),
|
||||
"03:33");
|
||||
EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
|
||||
"03:33:20");
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
86
test/color-test.cc
Normal file
86
test/color-test.cc
Normal file
@ -0,0 +1,86 @@
|
||||
// Formatting library for C++ - color tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/color.h"
|
||||
|
||||
#include "gtest-extra.h"
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
TEST(ColorsTest, 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::italic, "italic"),
|
||||
"\x1b[3mitalic\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"),
|
||||
"\x1b[4munderline\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"),
|
||||
"\x1b[9mstrikethrough\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
fmt::format(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
|
||||
"\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"),
|
||||
"\x1b[1mbold error\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue log"),
|
||||
"\x1b[38;2;000;000;255mblue log\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fmt::text_style(), "hi"), "hi");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "tred"),
|
||||
"\x1b[31mtred\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(bg(fmt::terminal_color::cyan), "tcyan"),
|
||||
"\x1b[46mtcyan\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::bright_green), "tbgreen"),
|
||||
"\x1b[92mtbgreen\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
|
||||
"\x1b[105mtbmagenta\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
|
||||
"\x1b[31mfoo\x1b[0m");
|
||||
}
|
79
test/compile-error-test/CMakeLists.txt
Normal file
79
test/compile-error-test/CMakeLists.txt
Normal file
@ -0,0 +1,79 @@
|
||||
# Test if compile errors are produced where necessary.
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
|
||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG} ${PEDANTIC_COMPILE_FLAGS})
|
||||
|
||||
function (generate_source result fragment)
|
||||
set(${result} "
|
||||
#define FMT_HEADER_ONLY 1
|
||||
#include \"fmt/format.h\"
|
||||
int main() {
|
||||
${fragment}
|
||||
}
|
||||
" PARENT_SCOPE)
|
||||
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})
|
||||
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("")
|
||||
|
||||
# Formatting a wide character with a narrow format string is forbidden.
|
||||
expect_compile_error("fmt::format(\"{}\", L'a');")
|
||||
|
||||
# Formatting a wide string with a narrow format string is forbidden.
|
||||
expect_compile_error("fmt::format(\"{}\", L\"foo\");")
|
||||
|
||||
# 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\");")
|
||||
|
||||
# Formatting a wide string with a narrow format string is forbidden.
|
||||
expect_compile_error("
|
||||
struct S {
|
||||
operator std::string() const { return std::string(); }
|
||||
};
|
||||
fmt::format(\"{}\", S());
|
||||
")
|
||||
|
||||
# Make sure that compiler features detected in the header
|
||||
# match the features detected in CMake.
|
||||
if (SUPPORTS_USER_DEFINED_LITERALS)
|
||||
set(supports_udl 1)
|
||||
else ()
|
||||
set(supports_udl 0)
|
||||
endif ()
|
||||
expect_compile("#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
|
||||
# error
|
||||
#endif")
|
166
test/compile-test.cc
Normal file
166
test/compile-test.cc
Normal file
@ -0,0 +1,166 @@
|
||||
// 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 <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 "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(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("{:{}.{}} {:{}}"));
|
||||
|
||||
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'
|
||||
}
|
||||
#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(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(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 {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<formattable> : formatter<const char*> {
|
||||
template <typename FormatContext>
|
||||
auto format(formattable, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return formatter<const char*>::format("foo", ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CompileTest, FormatUserDefinedType) {
|
||||
auto f = fmt::detail::compile<formattable>("{}");
|
||||
EXPECT_EQ(fmt::format(f, formattable()), "foo");
|
||||
}
|
||||
|
||||
TEST(CompileTest, EmptyFormatString) {
|
||||
auto f = fmt::detail::compile<>("");
|
||||
EXPECT_EQ(fmt::format(f), "");
|
||||
}
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
TEST(CompileTest, FormatDefault) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ull));
|
||||
EXPECT_EQ("true", fmt::format(FMT_COMPILE("{}"), true));
|
||||
EXPECT_EQ("x", fmt::format(FMT_COMPILE("{}"), 'x'));
|
||||
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()));
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatSpecs) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
||||
}
|
||||
|
||||
TEST(CompileTest, FormatTo) {
|
||||
char buf[8];
|
||||
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ("42", buf);
|
||||
}
|
||||
|
||||
TEST(CompileTest, TextAndArg) {
|
||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||
}
|
||||
#endif
|
@ -1,41 +0,0 @@
|
||||
# Test if compile errors are produced where necessary.
|
||||
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||
|
||||
function (expect_compile_error code)
|
||||
check_cxx_source_compiles("
|
||||
#include \"format.cc\"
|
||||
#include \"posix.h\"
|
||||
int main() {
|
||||
${code}
|
||||
}
|
||||
" compiles)
|
||||
set (does_compile ${compiles})
|
||||
# Unset the CMake cache variable compiles. Otherwise the compile test will
|
||||
# just use cached information next time it runs.
|
||||
unset(compiles CACHE)
|
||||
if (does_compile)
|
||||
message(FATAL_ERROR "No compile error for: ${code}")
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
# MakeArg doesn't accept [const] volatile char *.
|
||||
expect_compile_error("volatile char s[] = \"test\"; (fmt::internal::MakeArg<char>)(s);")
|
||||
expect_compile_error("const volatile char s[] = \"test\"; (fmt::internal::MakeArg<char>)(s);")
|
||||
|
||||
# MakeArg<char> doesn't accept wchar_t.
|
||||
expect_compile_error("fmt::internal::MakeValue<char>(L'a');")
|
||||
expect_compile_error("fmt::internal::MakeValue<char>(L\"test\");")
|
||||
|
||||
# Writing a wide character to a character stream Writer is forbidden.
|
||||
expect_compile_error("fmt::MemoryWriter() << L'a';")
|
||||
expect_compile_error("fmt::MemoryWriter() << fmt::pad(\"abc\", 5, L' ');")
|
||||
expect_compile_error("fmt::MemoryWriter() << fmt::pad(42, 5, L' ');")
|
||||
|
||||
# Formatting a wide character with a narrow format string is forbidden.
|
||||
expect_compile_error("fmt::format(\"{}\", L'a';")
|
||||
|
||||
expect_compile_error("FMT_STATIC_ASSERT(0 > 1, \"oops\");")
|
786
test/core-test.cc
Normal file
786
test/core-test.cc
Normal file
@ -0,0 +1,786 @@
|
||||
// Formatting library for C++ - core tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "gmock.h"
|
||||
#include "test-assert.h"
|
||||
|
||||
// Check if fmt/core.h compiles with windows.h included before it.
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
using fmt::basic_format_arg;
|
||||
using fmt::string_view;
|
||||
using fmt::detail::buffer;
|
||||
using fmt::detail::value;
|
||||
|
||||
using testing::_;
|
||||
using testing::StrictMock;
|
||||
|
||||
namespace {
|
||||
|
||||
struct test_struct {};
|
||||
|
||||
template <typename Context, typename T>
|
||||
basic_format_arg<Context> make_arg(const T& value) {
|
||||
return fmt::detail::make_arg<Context>(value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <typename Char> struct formatter<test_struct, Char> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
typedef std::back_insert_iterator<buffer<Char>> iterator;
|
||||
|
||||
auto format(test_struct, basic_format_context<iterator, char>& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
const Char* test = "test";
|
||||
return std::copy_n(test, std::strlen(test), ctx.out());
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
|
||||
TEST(BufferTest, Noncopyable) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value);
|
||||
# if !FMT_MSC_VER
|
||||
// std::is_copy_assignable is broken in MSVC2013.
|
||||
EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value);
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(BufferTest, Nonmoveable) {
|
||||
EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value);
|
||||
# if !FMT_MSC_VER
|
||||
// std::is_move_assignable is broken in MSVC2013.
|
||||
EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value);
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// A test buffer with a dummy grow method.
|
||||
template <typename T> struct test_buffer : buffer<T> {
|
||||
void grow(size_t capacity) { this->set(nullptr, capacity); }
|
||||
};
|
||||
|
||||
template <typename T> struct mock_buffer : buffer<T> {
|
||||
MOCK_METHOD1(do_grow, void(size_t capacity));
|
||||
|
||||
void grow(size_t capacity) {
|
||||
this->set(this->data(), capacity);
|
||||
do_grow(capacity);
|
||||
}
|
||||
|
||||
mock_buffer() {}
|
||||
mock_buffer(T* data) { this->set(data, 0); }
|
||||
mock_buffer(T* data, size_t capacity) { this->set(data, capacity); }
|
||||
};
|
||||
|
||||
TEST(BufferTest, Ctor) {
|
||||
{
|
||||
mock_buffer<int> buffer;
|
||||
EXPECT_EQ(nullptr, buffer.data());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
||||
}
|
||||
{
|
||||
int dummy;
|
||||
mock_buffer<int> buffer(&dummy);
|
||||
EXPECT_EQ(&dummy, &buffer[0]);
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
||||
}
|
||||
{
|
||||
int dummy;
|
||||
size_t capacity = std::numeric_limits<size_t>::max();
|
||||
mock_buffer<int> buffer(&dummy, capacity);
|
||||
EXPECT_EQ(&dummy, &buffer[0]);
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(capacity, buffer.capacity());
|
||||
}
|
||||
}
|
||||
|
||||
struct dying_buffer : test_buffer<int> {
|
||||
MOCK_METHOD0(die, void());
|
||||
~dying_buffer() { die(); }
|
||||
|
||||
private:
|
||||
virtual void avoid_weak_vtable();
|
||||
};
|
||||
|
||||
void dying_buffer::avoid_weak_vtable() {}
|
||||
|
||||
TEST(BufferTest, VirtualDtor) {
|
||||
typedef StrictMock<dying_buffer> stict_mock_buffer;
|
||||
stict_mock_buffer* mock_buffer = new stict_mock_buffer();
|
||||
EXPECT_CALL(*mock_buffer, die());
|
||||
buffer<int>* buffer = mock_buffer;
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
TEST(BufferTest, Access) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
buffer[0] = 11;
|
||||
EXPECT_EQ(11, buffer[0]);
|
||||
buffer[3] = 42;
|
||||
EXPECT_EQ(42, *(&buffer[0] + 3));
|
||||
const fmt::detail::buffer<char>& const_buffer = buffer;
|
||||
EXPECT_EQ(42, const_buffer[3]);
|
||||
}
|
||||
|
||||
TEST(BufferTest, Resize) {
|
||||
char data[123];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
buffer[10] = 42;
|
||||
EXPECT_EQ(42, buffer[10]);
|
||||
buffer.resize(20);
|
||||
EXPECT_EQ(20u, buffer.size());
|
||||
EXPECT_EQ(123u, buffer.capacity());
|
||||
EXPECT_EQ(42, buffer[10]);
|
||||
buffer.resize(5);
|
||||
EXPECT_EQ(5u, buffer.size());
|
||||
EXPECT_EQ(123u, buffer.capacity());
|
||||
EXPECT_EQ(42, buffer[10]);
|
||||
// Check if resize calls grow.
|
||||
EXPECT_CALL(buffer, do_grow(124));
|
||||
buffer.resize(124);
|
||||
EXPECT_CALL(buffer, do_grow(200));
|
||||
buffer.resize(200);
|
||||
}
|
||||
|
||||
TEST(BufferTest, Clear) {
|
||||
test_buffer<char> buffer;
|
||||
buffer.resize(20);
|
||||
buffer.resize(0);
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(20u, buffer.capacity());
|
||||
}
|
||||
|
||||
TEST(BufferTest, Append) {
|
||||
char data[15];
|
||||
mock_buffer<char> buffer(data, 10);
|
||||
const char* test = "test";
|
||||
buffer.append(test, test + 5);
|
||||
EXPECT_STREQ(test, &buffer[0]);
|
||||
EXPECT_EQ(5u, buffer.size());
|
||||
buffer.resize(10);
|
||||
EXPECT_CALL(buffer, do_grow(12));
|
||||
buffer.append(test, test + 2);
|
||||
EXPECT_EQ('t', buffer[10]);
|
||||
EXPECT_EQ('e', buffer[11]);
|
||||
EXPECT_EQ(12u, buffer.size());
|
||||
}
|
||||
|
||||
TEST(BufferTest, AppendAllocatesEnoughStorage) {
|
||||
char data[19];
|
||||
mock_buffer<char> buffer(data, 10);
|
||||
const char* test = "abcdefgh";
|
||||
buffer.resize(10);
|
||||
EXPECT_CALL(buffer, do_grow(19));
|
||||
buffer.append(test, test + 9);
|
||||
}
|
||||
|
||||
TEST(ArgTest, FormatArgs) {
|
||||
fmt::format_args args;
|
||||
EXPECT_FALSE(args.get(1));
|
||||
}
|
||||
|
||||
struct custom_context {
|
||||
using char_type = char;
|
||||
using parse_context_type = fmt::format_parse_context;
|
||||
|
||||
template <typename T> struct formatter_type {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
const char* format(const T&, custom_context& ctx) {
|
||||
ctx.called = true;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
bool called;
|
||||
fmt::format_parse_context ctx;
|
||||
|
||||
fmt::format_parse_context& parse_context() { return ctx; }
|
||||
void advance_to(const char*) {}
|
||||
};
|
||||
|
||||
TEST(ArgTest, MakeValueWithCustomContext) {
|
||||
test_struct t;
|
||||
fmt::detail::value<custom_context> arg(
|
||||
fmt::detail::arg_mapper<custom_context>().map(t));
|
||||
custom_context ctx = {false, fmt::format_parse_context("")};
|
||||
arg.custom.format(&t, ctx.parse_context(), ctx);
|
||||
EXPECT_TRUE(ctx.called);
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template <typename Char>
|
||||
bool operator==(custom_value<Char> lhs, custom_value<Char> rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
} // namespace detail
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
// Use a unique result type to make sure that there are no undesirable
|
||||
// conversions.
|
||||
struct test_result {};
|
||||
|
||||
template <typename T> struct mock_visitor {
|
||||
template <typename U> struct result { typedef test_result type; };
|
||||
|
||||
mock_visitor() {
|
||||
ON_CALL(*this, visit(_)).WillByDefault(testing::Return(test_result()));
|
||||
}
|
||||
|
||||
MOCK_METHOD1_T(visit, test_result(T value));
|
||||
MOCK_METHOD0_T(unexpected, void());
|
||||
|
||||
test_result operator()(T value) { return visit(value); }
|
||||
|
||||
template <typename U> test_result operator()(U) {
|
||||
unexpected();
|
||||
return test_result();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct visit_type { typedef T Type; };
|
||||
|
||||
#define VISIT_TYPE(Type_, visit_type_) \
|
||||
template <> struct visit_type<Type_> { typedef visit_type_ Type; }
|
||||
|
||||
VISIT_TYPE(signed char, int);
|
||||
VISIT_TYPE(unsigned char, unsigned);
|
||||
VISIT_TYPE(short, int);
|
||||
VISIT_TYPE(unsigned short, unsigned);
|
||||
|
||||
#if LONG_MAX == INT_MAX
|
||||
VISIT_TYPE(long, int);
|
||||
VISIT_TYPE(unsigned long, unsigned);
|
||||
#else
|
||||
VISIT_TYPE(long, long long);
|
||||
VISIT_TYPE(unsigned long, unsigned long long);
|
||||
#endif
|
||||
|
||||
#define CHECK_ARG_(Char, expected, value) \
|
||||
{ \
|
||||
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
||||
EXPECT_CALL(visitor, visit(expected)); \
|
||||
typedef std::back_insert_iterator<buffer<Char>> iterator; \
|
||||
fmt::visit_format_arg( \
|
||||
visitor, make_arg<fmt::basic_format_context<iterator, Char>>(value)); \
|
||||
}
|
||||
|
||||
#define CHECK_ARG(value, typename_) \
|
||||
{ \
|
||||
typedef decltype(value) value_type; \
|
||||
typename_ visit_type<value_type>::Type expected = value; \
|
||||
CHECK_ARG_(char, expected, value) \
|
||||
CHECK_ARG_(wchar_t, expected, value) \
|
||||
}
|
||||
|
||||
template <typename T> class NumericArgTest : public testing::Test {};
|
||||
|
||||
typedef ::testing::Types<bool, signed char, unsigned char, signed,
|
||||
unsigned short, int, unsigned, long, unsigned long,
|
||||
long long, unsigned long long, float, double,
|
||||
long double>
|
||||
Types;
|
||||
TYPED_TEST_CASE(NumericArgTest, Types);
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, T>::type test_value() {
|
||||
return static_cast<T>(42);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value, T>::type
|
||||
test_value() {
|
||||
return static_cast<T>(4.2);
|
||||
}
|
||||
|
||||
TYPED_TEST(NumericArgTest, MakeAndVisit) {
|
||||
CHECK_ARG(test_value<TypeParam>(), typename);
|
||||
CHECK_ARG(std::numeric_limits<TypeParam>::min(), typename);
|
||||
CHECK_ARG(std::numeric_limits<TypeParam>::max(), typename);
|
||||
}
|
||||
|
||||
TEST(ArgTest, CharArg) {
|
||||
CHECK_ARG_(char, 'a', 'a');
|
||||
CHECK_ARG_(wchar_t, L'a', 'a');
|
||||
CHECK_ARG_(wchar_t, L'a', L'a');
|
||||
}
|
||||
|
||||
TEST(ArgTest, StringArg) {
|
||||
char str_data[] = "test";
|
||||
char* str = str_data;
|
||||
const char* cstr = str;
|
||||
CHECK_ARG_(char, cstr, str);
|
||||
|
||||
string_view sref(str);
|
||||
CHECK_ARG_(char, sref, std::string(str));
|
||||
}
|
||||
|
||||
TEST(ArgTest, WStringArg) {
|
||||
wchar_t str_data[] = L"test";
|
||||
wchar_t* str = str_data;
|
||||
const wchar_t* cstr = str;
|
||||
|
||||
fmt::wstring_view sref(str);
|
||||
CHECK_ARG_(wchar_t, cstr, str);
|
||||
CHECK_ARG_(wchar_t, cstr, cstr);
|
||||
CHECK_ARG_(wchar_t, sref, std::wstring(str));
|
||||
CHECK_ARG_(wchar_t, sref, fmt::wstring_view(str));
|
||||
}
|
||||
|
||||
TEST(ArgTest, PointerArg) {
|
||||
void* p = nullptr;
|
||||
const void* cp = nullptr;
|
||||
CHECK_ARG_(char, cp, p);
|
||||
CHECK_ARG_(wchar_t, cp, p);
|
||||
CHECK_ARG(cp, );
|
||||
}
|
||||
|
||||
struct check_custom {
|
||||
test_result operator()(
|
||||
fmt::basic_format_arg<fmt::format_context>::handle h) const {
|
||||
struct test_buffer : fmt::detail::buffer<char> {
|
||||
char data[10];
|
||||
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
|
||||
void grow(size_t) {}
|
||||
} buffer;
|
||||
fmt::detail::buffer<char>& base = buffer;
|
||||
fmt::format_parse_context parse_ctx("");
|
||||
fmt::format_context ctx(std::back_inserter(base), fmt::format_args());
|
||||
h.format(parse_ctx, ctx);
|
||||
EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
|
||||
return test_result();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ArgTest, CustomArg) {
|
||||
test_struct test;
|
||||
typedef mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>
|
||||
visitor;
|
||||
testing::StrictMock<visitor> v;
|
||||
EXPECT_CALL(v, visit(_)).WillOnce(testing::Invoke(check_custom()));
|
||||
fmt::visit_format_arg(v, make_arg<fmt::format_context>(test));
|
||||
}
|
||||
|
||||
TEST(ArgTest, VisitInvalidArg) {
|
||||
testing::StrictMock<mock_visitor<fmt::monostate>> visitor;
|
||||
EXPECT_CALL(visitor, visit(_));
|
||||
fmt::basic_format_arg<fmt::format_context> arg;
|
||||
fmt::visit_format_arg(visitor, arg);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, Basic) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc1");
|
||||
store.push_back(1.5f);
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("42 and abc1 and 1.5", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, StringsAndRefs) {
|
||||
// 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';
|
||||
|
||||
std::string 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(FormatDynArgsTest, CustomFormat) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
custom_type c{};
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(std::cref(c));
|
||||
++c.i;
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, NamedInt) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("a1", 42));
|
||||
std::string result = fmt::vformat("{a1}", store);
|
||||
EXPECT_EQ("42", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, NamedStrings) {
|
||||
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';
|
||||
|
||||
std::string result = fmt::vformat("{a1} and {a2}", store);
|
||||
|
||||
EXPECT_EQ("1234567890 and X234567890", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, NamedArgByRef) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
|
||||
// Note: fmt::arg() constructs an object which holds a reference
|
||||
// to its value. It's not an aggregate, so it doesn't extend the
|
||||
// reference lifetime. As a result, it's a very bad idea passing temporary
|
||||
// as a named argument value. Only GCC with optimization level >0
|
||||
// complains about this.
|
||||
//
|
||||
// A real life usecase is when you have both name and value alive
|
||||
// guarantee their lifetime and thus don't want them to be copied into
|
||||
// storages.
|
||||
int a1_val{42};
|
||||
auto a1 = fmt::arg("a1_", a1_val);
|
||||
store.push_back("abc");
|
||||
store.push_back(1.5f);
|
||||
store.push_back(std::cref(a1));
|
||||
|
||||
std::string result = fmt::vformat("{a1_} and {} and {} and {}", store);
|
||||
|
||||
EXPECT_EQ("42 and abc and 1.5 and 42", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, NamedCustomFormat) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
custom_type c{};
|
||||
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;
|
||||
|
||||
std::string result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, Clear) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
|
||||
std::string 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(FormatDynArgsTest, 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));
|
||||
std::string 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(FormatDynArgsTest, ThrowOnCopy) {
|
||||
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(StringViewTest, ValueType) {
|
||||
static_assert(std::is_same<string_view::value_type, char>::value, "");
|
||||
}
|
||||
|
||||
TEST(StringViewTest, Length) {
|
||||
// Test that string_view::size() returns string length, not buffer size.
|
||||
char str[100] = "some string";
|
||||
EXPECT_EQ(std::strlen(str), string_view(str).size());
|
||||
EXPECT_LT(std::strlen(str), sizeof(str));
|
||||
}
|
||||
|
||||
// Check string_view's comparison operator.
|
||||
template <template <typename> class Op> void check_op() {
|
||||
const char* inputs[] = {"foo", "fop", "fo"};
|
||||
size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
|
||||
for (size_t i = 0; i < num_inputs; ++i) {
|
||||
for (size_t j = 0; j < num_inputs; ++j) {
|
||||
string_view lhs(inputs[i]), rhs(inputs[j]);
|
||||
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringViewTest, Compare) {
|
||||
EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
|
||||
EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
|
||||
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
||||
EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
|
||||
EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
|
||||
check_op<std::equal_to>();
|
||||
check_op<std::not_equal_to>();
|
||||
check_op<std::less>();
|
||||
check_op<std::less_equal>();
|
||||
check_op<std::greater>();
|
||||
check_op<std::greater_equal>();
|
||||
}
|
||||
|
||||
struct enabled_formatter {};
|
||||
struct disabled_formatter {};
|
||||
struct disabled_formatter_convertible {
|
||||
operator int() const { return 42; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<enabled_formatter> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CoreTest, HasFormatter) {
|
||||
using fmt::has_formatter;
|
||||
using context = fmt::format_context;
|
||||
static_assert(has_formatter<enabled_formatter, context>::value, "");
|
||||
static_assert(!has_formatter<disabled_formatter, context>::value, "");
|
||||
static_assert(!has_formatter<disabled_formatter_convertible, context>::value,
|
||||
"");
|
||||
}
|
||||
|
||||
struct convertible_to_int {
|
||||
operator int() const { return 42; }
|
||||
};
|
||||
|
||||
struct convertible_to_c_string {
|
||||
operator const char*() const { return "foo"; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<convertible_to_int> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return std::copy_n("foo", 3, ctx.out());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct formatter<convertible_to_c_string> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(convertible_to_c_string, format_context& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
return std::copy_n("bar", 3, ctx.out());
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CoreTest, FormatterOverridesImplicitConversion) {
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar");
|
||||
}
|
||||
|
||||
namespace my_ns {
|
||||
template <typename Char> class my_string {
|
||||
public:
|
||||
my_string(const Char* s) : s_(s) {}
|
||||
const Char* data() const FMT_NOEXCEPT { return s_.data(); }
|
||||
size_t length() const FMT_NOEXCEPT { return s_.size(); }
|
||||
operator const Char*() const { return s_.c_str(); }
|
||||
|
||||
private:
|
||||
std::basic_string<Char> s_;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline fmt::basic_string_view<Char> to_string_view(const my_string<Char>& s)
|
||||
FMT_NOEXCEPT {
|
||||
return {s.data(), s.length()};
|
||||
}
|
||||
|
||||
struct non_string {};
|
||||
} // namespace my_ns
|
||||
|
||||
template <typename T> class IsStringTest : public testing::Test {};
|
||||
|
||||
typedef ::testing::Types<char, wchar_t, char16_t, char32_t> StringCharTypes;
|
||||
TYPED_TEST_CASE(IsStringTest, StringCharTypes);
|
||||
|
||||
namespace {
|
||||
template <typename Char>
|
||||
struct derived_from_string_view : fmt::basic_string_view<Char> {};
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST(IsStringTest, IsString) {
|
||||
EXPECT_TRUE(fmt::detail::is_string<TypeParam*>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<const TypeParam*>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<TypeParam[2]>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<const TypeParam[2]>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<std::basic_string<TypeParam>>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<fmt::basic_string_view<TypeParam>>::value);
|
||||
EXPECT_TRUE(
|
||||
fmt::detail::is_string<derived_from_string_view<TypeParam>>::value);
|
||||
using string_view = fmt::detail::std_string_view<TypeParam>;
|
||||
EXPECT_TRUE(std::is_empty<string_view>::value !=
|
||||
fmt::detail::is_string<string_view>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<my_ns::my_string<TypeParam>>::value);
|
||||
EXPECT_FALSE(fmt::detail::is_string<my_ns::non_string>::value);
|
||||
}
|
||||
|
||||
TEST(CoreTest, Format) {
|
||||
// This should work without including fmt/format.h.
|
||||
#ifdef FMT_FORMAT_H_
|
||||
# error fmt/format.h must not be included in the core test
|
||||
#endif
|
||||
EXPECT_EQ(fmt::format("{}", 42), "42");
|
||||
}
|
||||
|
||||
TEST(CoreTest, FormatTo) {
|
||||
// This should work without including fmt/format.h.
|
||||
#ifdef FMT_FORMAT_H_
|
||||
# error fmt/format.h must not be included in the core test
|
||||
#endif
|
||||
std::string s;
|
||||
fmt::format_to(std::back_inserter(s), "{}", 42);
|
||||
EXPECT_EQ(s, "42");
|
||||
}
|
||||
|
||||
TEST(CoreTest, ToStringViewForeignStrings) {
|
||||
using namespace my_ns;
|
||||
EXPECT_EQ(to_string_view(my_string<char>("42")), "42");
|
||||
fmt::detail::type type =
|
||||
fmt::detail::mapped_type_constant<my_string<char>,
|
||||
fmt::format_context>::value;
|
||||
EXPECT_EQ(type, fmt::detail::type::string_type);
|
||||
}
|
||||
|
||||
TEST(CoreTest, FormatForeignStrings) {
|
||||
using namespace my_ns;
|
||||
EXPECT_EQ(fmt::format(my_string<char>("{}"), 42), "42");
|
||||
}
|
||||
|
||||
struct implicitly_convertible_to_string {
|
||||
operator std::string() const { return "foo"; }
|
||||
};
|
||||
|
||||
struct implicitly_convertible_to_string_view {
|
||||
operator fmt::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatImplicitlyConvertibleToStringView) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
|
||||
}
|
||||
|
||||
// std::is_constructible is broken in MSVC until version 2015.
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER >= 1900
|
||||
struct explicitly_convertible_to_string_view {
|
||||
explicit operator fmt::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStringView) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view()));
|
||||
}
|
||||
|
||||
# ifdef FMT_USE_STRING_VIEW
|
||||
struct explicitly_convertible_to_std_string_view {
|
||||
explicit operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
|
||||
EXPECT_EQ("foo",
|
||||
fmt::format("{}", explicitly_convertible_to_std_string_view()));
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
struct disabled_rvalue_conversion {
|
||||
operator const char*() const& { return "foo"; }
|
||||
operator const char*() & { return "foo"; }
|
||||
operator const char*() const&& = delete;
|
||||
operator const char*() && = delete;
|
||||
};
|
||||
|
||||
TEST(FormatterTest, DisabledRValueConversion) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
|
||||
}
|
73
test/cuda-test/CMakeLists.txt
Normal file
73
test/cuda-test/CMakeLists.txt
Normal file
@ -0,0 +1,73 @@
|
||||
# We can find some usecases which follow the guide of CMake which uses
|
||||
# `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake
|
||||
# built-in functions use NVCC.
|
||||
|
||||
# See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement
|
||||
#
|
||||
# However, this requires CMake version 3.10 or higher and we can't be sure most
|
||||
# of the CUDA projects are using those.
|
||||
#
|
||||
# This test relies on `find_package(CUDA)` in the parent CMake config.
|
||||
|
||||
# These can be updated when NVCC becomes ready for C++ 17 features
|
||||
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
||||
set(CMAKE_CUDA_STANDARD 14)
|
||||
set(CMAKE_CUDA_STANDARD_REQUIRED 14)
|
||||
|
||||
# In this test, we assume that the user is going to compile CUDA source code
|
||||
# with some libraries (fmt in this case).
|
||||
#
|
||||
# In addition to that, this test invokes both the C++ host compiler and NVCC
|
||||
# by providing another (non-CUDA) C++ source code.
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
||||
# https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html
|
||||
list(APPEND CUDA_NVCC_FLAGS "-std=c++14")
|
||||
if (MSVC)
|
||||
# This is the solution of pytorch:
|
||||
# https://github.com/pytorch/pytorch/pull/7118
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14")
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus")
|
||||
# for the reason of this -Xcompiler options, see below.
|
||||
endif ()
|
||||
cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
|
||||
target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
|
||||
if (MSVC)
|
||||
# This part is for (non-CUDA) C++ code. MSVC can define incorrect
|
||||
# `__cplusplus` macro. Fix for the issue is to use additional compiler flag.
|
||||
#
|
||||
# See Also:
|
||||
# https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
|
||||
# https://github.com/Microsoft/vscode-cpptools/issues/2595
|
||||
target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-)
|
||||
endif ()
|
||||
else()
|
||||
# now using a "new" way of handling CUDA
|
||||
add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
|
||||
set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
|
||||
target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
|
||||
if (MSVC)
|
||||
# with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will
|
||||
# not set __cplusplus correctly anyway, while nvcc will ignore it.
|
||||
# If specified for nvcc on the command line as '-std=c++14' nvcc will emit this
|
||||
# message instead:
|
||||
# nvcc warning : The -std=c++14 flag is not supported with the configured host
|
||||
# compiler. Flag will be ignored.
|
||||
set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY
|
||||
COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus)
|
||||
set_property(SOURCE cpp14.cc APPEND PROPERTY
|
||||
COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD)
|
||||
message(STATUS "cuda_standard: ${IN_USE_CUDA_STANDARD}")
|
||||
|
||||
get_target_property(IN_USE_CUDA_STANDARD_REQUIRED
|
||||
fmt-in-cuda-test CUDA_STANDARD_REQUIRED)
|
||||
message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}")
|
||||
|
||||
# We don't use PUBLIC or other keyword for reasons explained in the
|
||||
# CUDA_LINK_LIBRARIES_KEYWORD section in
|
||||
# https://cmake.org/cmake/help/latest/module/FindCUDA.html
|
||||
target_link_libraries(fmt-in-cuda-test fmt::fmt)
|
||||
|
11
test/cuda-test/cpp14.cc
Normal file
11
test/cuda-test/cpp14.cc
Normal file
@ -0,0 +1,11 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
// The purpose of this part is to ensure NVCC's host compiler also supports
|
||||
// the standard version. See 'cuda-cpp14.cu'.
|
||||
//
|
||||
// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
|
||||
static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler");
|
||||
|
||||
auto make_message_cpp() -> std::string {
|
||||
return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus);
|
||||
}
|
28
test/cuda-test/cuda-cpp14.cu
Normal file
28
test/cuda-test/cuda-cpp14.cu
Normal file
@ -0,0 +1,28 @@
|
||||
// Direct NVCC command line example:
|
||||
//
|
||||
// nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \
|
||||
// -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus
|
||||
|
||||
// Ensure that we are using the latest C++ standard for NVCC
|
||||
// The version is C++14
|
||||
//
|
||||
// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support
|
||||
// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
|
||||
static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc");
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <iostream>
|
||||
|
||||
extern auto make_message_cpp() -> std::string;
|
||||
extern auto make_message_cuda() -> std::string;
|
||||
|
||||
int main() {
|
||||
std::cout << make_message_cuda() << std::endl;
|
||||
std::cout << make_message_cpp() << std::endl;
|
||||
}
|
||||
|
||||
auto make_message_cuda() -> std::string {
|
||||
return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus);
|
||||
}
|
58
test/custom-formatter-test.cc
Normal file
58
test/custom-formatter-test.cc
Normal file
@ -0,0 +1,58 @@
|
||||
// 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
|
17
test/find-package-test/CMakeLists.txt
Normal file
17
test/find-package-test/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
project(fmt-test)
|
||||
|
||||
find_package(FMT REQUIRED)
|
||||
|
||||
add_executable(library-test main.cc)
|
||||
target_link_libraries(library-test fmt::fmt)
|
||||
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
target_include_directories(library-test PUBLIC SYSTEM .)
|
||||
|
||||
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})
|
||||
target_include_directories(header-only-test PUBLIC SYSTEM .)
|
||||
endif ()
|
6
test/find-package-test/main.cc
Normal file
6
test/find-package-test/main.cc
Normal file
@ -0,0 +1,6 @@
|
||||
#include "fmt/format.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
for(int i = 0; i < argc; ++i)
|
||||
fmt::print("{}: {}\n", i, argv[i]);
|
||||
}
|
855
test/format
Normal file
855
test/format
Normal file
@ -0,0 +1,855 @@
|
||||
// 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_
|
6
test/format-dyn-args-test.cc
Normal file
6
test/format-dyn-args-test.cc
Normal file
@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2020 Vladimir Solontsov
|
||||
// SPDX-License-Identifier: MIT Licence
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "gtest-extra.h"
|
@ -1,65 +1,369 @@
|
||||
/*
|
||||
Formatting library implementation tests.
|
||||
|
||||
Copyright (c) 2012-2014, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY 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.
|
||||
*/
|
||||
// Formatting library for C++ - formatting library implementation tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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-specific stuff.
|
||||
#include "format.cc"
|
||||
|
||||
// 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"
|
||||
|
||||
#undef max
|
||||
|
||||
using fmt::detail::bigint;
|
||||
using fmt::detail::fp;
|
||||
using fmt::detail::max_value;
|
||||
|
||||
static_assert(!std::is_copy_constructible<bigint>::value, "");
|
||||
static_assert(!std::is_copy_assignable<bigint>::value, "");
|
||||
|
||||
TEST(BigIntTest, Construct) {
|
||||
EXPECT_EQ("", fmt::format("{}", bigint()));
|
||||
EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
|
||||
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Compare) {
|
||||
bigint n1(42);
|
||||
bigint n2(42);
|
||||
EXPECT_EQ(compare(n1, n2), 0);
|
||||
n2 <<= 32;
|
||||
EXPECT_LT(compare(n1, n2), 0);
|
||||
bigint n3(43);
|
||||
EXPECT_LT(compare(n1, n3), 0);
|
||||
EXPECT_GT(compare(n3, n1), 0);
|
||||
bigint n4(42 * 0x100000001);
|
||||
EXPECT_LT(compare(n2, n4), 0);
|
||||
EXPECT_GT(compare(n4, n2), 0);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, AddCompare) {
|
||||
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);
|
||||
EXPECT_GT(add_compare(bigint(1) <<= 32, bigint(0), bigint(0xffffffff)), 0);
|
||||
EXPECT_GT(add_compare(bigint(0), bigint(1) <<= 32, bigint(0xffffffff)), 0);
|
||||
EXPECT_GT(add_compare(bigint(42), bigint(1), bigint(42)), 0);
|
||||
EXPECT_GT(add_compare(bigint(0xffffffff), bigint(1), bigint(0xffffffff)), 0);
|
||||
EXPECT_LT(add_compare(bigint(10), bigint(10), bigint(22)), 0);
|
||||
EXPECT_LT(add_compare(bigint(0x100000010), bigint(0x100000010),
|
||||
bigint(0x300000010)),
|
||||
0);
|
||||
EXPECT_GT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000000)),
|
||||
0);
|
||||
EXPECT_EQ(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000001)),
|
||||
0);
|
||||
EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000002)),
|
||||
0);
|
||||
EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000003)),
|
||||
0);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, ShiftLeft) {
|
||||
bigint n(0x42);
|
||||
n <<= 0;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n <<= 1;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n <<= 25;
|
||||
EXPECT_EQ("108000000", fmt::format("{}", n));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Multiply) {
|
||||
bigint n(0x42);
|
||||
EXPECT_THROW(n *= 0, assertion_failure);
|
||||
n *= 1;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n *= 2;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n *= 0x12345678;
|
||||
EXPECT_EQ("962fc95e0", fmt::format("{}", n));
|
||||
bigint bigmax(max_value<uint32_t>());
|
||||
bigmax *= max_value<uint32_t>();
|
||||
EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
|
||||
bigmax.assign(max_value<uint64_t>());
|
||||
bigmax *= max_value<uint64_t>();
|
||||
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Accumulator) {
|
||||
fmt::detail::accumulator acc;
|
||||
EXPECT_EQ(acc.lower, 0);
|
||||
EXPECT_EQ(acc.upper, 0);
|
||||
acc.upper = 12;
|
||||
acc.lower = 34;
|
||||
EXPECT_EQ(static_cast<uint32_t>(acc), 34);
|
||||
acc += 56;
|
||||
EXPECT_EQ(acc.lower, 90);
|
||||
acc += fmt::detail::max_value<uint64_t>();
|
||||
EXPECT_EQ(acc.upper, 13);
|
||||
EXPECT_EQ(acc.lower, 89);
|
||||
acc >>= 32;
|
||||
EXPECT_EQ(acc.upper, 0);
|
||||
EXPECT_EQ(acc.lower, 13 * 0x100000000);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Square) {
|
||||
bigint n0(0);
|
||||
n0.square();
|
||||
EXPECT_EQ("0", fmt::format("{}", n0));
|
||||
bigint n1(0x100);
|
||||
n1.square();
|
||||
EXPECT_EQ("10000", fmt::format("{}", n1));
|
||||
bigint n2(0xfffffffff);
|
||||
n2.square();
|
||||
EXPECT_EQ("ffffffffe000000001", fmt::format("{}", n2));
|
||||
bigint n3(max_value<uint64_t>());
|
||||
n3.square();
|
||||
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", n3));
|
||||
bigint n4;
|
||||
n4.assign_pow10(10);
|
||||
EXPECT_EQ("2540be400", fmt::format("{}", n4));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignZeroDivisor) {
|
||||
bigint zero(0);
|
||||
EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
|
||||
EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignSelf) {
|
||||
bigint n(100);
|
||||
EXPECT_THROW(n.divmod_assign(n), assertion_failure);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignUnaligned) {
|
||||
// (42 << 340) / pow(10, 100):
|
||||
bigint n1(42);
|
||||
n1 <<= 340;
|
||||
bigint n2;
|
||||
n2.assign_pow10(100);
|
||||
int result = n1.divmod_assign(n2);
|
||||
EXPECT_EQ(result, 9406);
|
||||
EXPECT_EQ("10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96",
|
||||
fmt::format("{}", n1));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssign) {
|
||||
// 100 / 10:
|
||||
bigint n1(100);
|
||||
int result = n1.divmod_assign(bigint(10));
|
||||
EXPECT_EQ(result, 10);
|
||||
EXPECT_EQ("0", fmt::format("{}", n1));
|
||||
// pow(10, 100) / (42 << 320):
|
||||
n1.assign_pow10(100);
|
||||
result = n1.divmod_assign(bigint(42) <<= 320);
|
||||
EXPECT_EQ(result, 111);
|
||||
EXPECT_EQ("13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96",
|
||||
fmt::format("{}", n1));
|
||||
// 42 / 100:
|
||||
bigint n2(42);
|
||||
n1.assign_pow10(2);
|
||||
result = n2.divmod_assign(n1);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ("2a", fmt::format("{}", n2));
|
||||
}
|
||||
|
||||
template <bool is_iec559> void run_double_tests() {
|
||||
fmt::print("warning: double is not IEC559, skipping FP tests\n");
|
||||
}
|
||||
|
||||
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) {
|
||||
run_double_tests<std::numeric_limits<double>::is_iec559>();
|
||||
}
|
||||
|
||||
TEST(FPTest, 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) {
|
||||
auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
|
||||
EXPECT_EQ(v.f, 123u * 56u);
|
||||
EXPECT_EQ(v.e, 4 + 7 + 64);
|
||||
v = fp(123ULL << 32, 4) * fp(567ULL << 31, 8);
|
||||
EXPECT_EQ(v.f, (123 * 567 + 1u) / 2);
|
||||
EXPECT_EQ(v.e, 4 + 8 + 64);
|
||||
}
|
||||
|
||||
TEST(FPTest, GetCachedPower) {
|
||||
typedef std::numeric_limits<double> limits;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FPTest, GetRoundDirection) {
|
||||
using fmt::detail::get_round_direction;
|
||||
using fmt::detail::round_direction;
|
||||
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(100, 51, 0));
|
||||
EXPECT_EQ(round_direction::down, get_round_direction(100, 40, 10));
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(100, 60, 10));
|
||||
for (size_t i = 41; i < 60; ++i)
|
||||
EXPECT_EQ(round_direction::unknown, get_round_direction(100, i, 10));
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
|
||||
// Check that remainder + error doesn't overflow.
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 2));
|
||||
// Check that 2 * (remainder + error) doesn't overflow.
|
||||
EXPECT_EQ(round_direction::unknown,
|
||||
get_round_direction(max, max / 2 + 1, max / 2));
|
||||
// Check that remainder - error doesn't overflow.
|
||||
EXPECT_EQ(round_direction::unknown, get_round_direction(100, 40, 41));
|
||||
// Check that 2 * (remainder - error) doesn't overflow.
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
|
||||
}
|
||||
|
||||
TEST(FPTest, FixedHandler) {
|
||||
struct handler : fmt::detail::fixed_handler {
|
||||
char buffer[10];
|
||||
handler(int prec = 0) : fmt::detail::fixed_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);
|
||||
namespace digits = fmt::detail::digits;
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done);
|
||||
// Check that divisor - error doesn't overflow.
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, 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);
|
||||
}
|
||||
|
||||
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
|
||||
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) {
|
||||
using fmt::internal::Arg;
|
||||
Arg arg = Arg();
|
||||
arg.type = Arg::LONG_LONG;
|
||||
arg.long_long_value = std::numeric_limits<fmt::LongLong>::max();
|
||||
fmt::ArgConverter<fmt::LongLong>(arg, 'd').visit(arg);
|
||||
EXPECT_EQ(Arg::LONG_LONG, arg.type);
|
||||
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 (fmt::internal::FPUtil::isnegative(-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 = 0;
|
||||
char* message = nullptr;
|
||||
char buffer[BUFFER_SIZE];
|
||||
EXPECT_ASSERT(fmt::safe_strerror(EDOM, message = 0, 0), "invalid buffer");
|
||||
EXPECT_ASSERT(fmt::safe_strerror(EDOM, message = buffer, 0), "invalid buffer");
|
||||
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
|
||||
@ -69,17 +373,19 @@ TEST(FormatTest, StrError) {
|
||||
int error_code = EDOM;
|
||||
#endif
|
||||
|
||||
int result = fmt::safe_strerror(error_code, message = buffer, BUFFER_SIZE);
|
||||
EXPECT_EQ(0, result);
|
||||
std::size_t message_size = std::strlen(message);
|
||||
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.
|
||||
#ifndef __MINGW32__
|
||||
result = fmt::safe_strerror(error_code, message = buffer, message_size);
|
||||
#if !defined(__MINGW32__) && !defined(__sun)
|
||||
result =
|
||||
fmt::detail::safe_strerror(error_code, message = buffer, message_size);
|
||||
EXPECT_EQ(ERANGE, result);
|
||||
result = fmt::safe_strerror(error_code, message = buffer, 1);
|
||||
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);
|
||||
@ -89,25 +395,62 @@ TEST(FormatTest, StrError) {
|
||||
TEST(FormatTest, FormatErrorCode) {
|
||||
std::string msg = "error 42", sep = ": ";
|
||||
{
|
||||
fmt::MemoryWriter w;
|
||||
w << "garbage";
|
||||
fmt::format_error_code(w, 42, "test");
|
||||
EXPECT_EQ("test: " + msg, w.str());
|
||||
fmt::memory_buffer buffer;
|
||||
format_to(buffer, "garbage");
|
||||
fmt::detail::format_error_code(buffer, 42, "test");
|
||||
EXPECT_EQ("test: " + msg, to_string(buffer));
|
||||
}
|
||||
{
|
||||
fmt::MemoryWriter w;
|
||||
std::string prefix(
|
||||
fmt::internal::INLINE_BUFFER_SIZE - msg.size() - sep.size() + 1, 'x');
|
||||
fmt::format_error_code(w, 42, prefix);
|
||||
EXPECT_EQ(msg, w.str());
|
||||
fmt::memory_buffer buffer;
|
||||
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size() + 1,
|
||||
'x');
|
||||
fmt::detail::format_error_code(buffer, 42, prefix);
|
||||
EXPECT_EQ(msg, to_string(buffer));
|
||||
}
|
||||
{
|
||||
fmt::MemoryWriter w;
|
||||
std::string prefix(
|
||||
fmt::internal::INLINE_BUFFER_SIZE - msg.size() - sep.size(), 'x');
|
||||
fmt::format_error_code(w, 42, prefix);
|
||||
EXPECT_EQ(prefix + sep + msg, w.str());
|
||||
std::size_t size = fmt::internal::INLINE_BUFFER_SIZE;
|
||||
EXPECT_EQ(size, w.size());
|
||||
int codes[] = {42, -1};
|
||||
for (size_t i = 0, n = sizeof(codes) / sizeof(*codes); i < n; ++i) {
|
||||
// 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');
|
||||
fmt::detail::format_error_code(buffer, codes[i], prefix);
|
||||
EXPECT_EQ(prefix + sep + msg, to_string(buffer));
|
||||
size_t size = fmt::inline_buffer_size;
|
||||
EXPECT_EQ(size, buffer.size());
|
||||
buffer.resize(0);
|
||||
// Test with a message that doesn't fit into the buffer.
|
||||
prefix += 'x';
|
||||
fmt::detail::format_error_code(buffer, codes[i], prefix);
|
||||
EXPECT_EQ(msg, to_string(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FormatTest, CountCodePoints) {
|
||||
EXPECT_EQ(4,
|
||||
fmt::detail::count_code_points(
|
||||
fmt::basic_string_view<fmt::detail::char8_type>(
|
||||
reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
|
||||
}
|
||||
|
||||
// Tests fmt::detail::count_digits for integer type Int.
|
||||
template <typename Int> void test_count_digits() {
|
||||
for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i));
|
||||
for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) {
|
||||
n *= 10;
|
||||
EXPECT_EQ(i, fmt::detail::count_digits(n - 1));
|
||||
EXPECT_EQ(i + 1, fmt::detail::count_digits(n));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UtilTest, CountDigits) {
|
||||
test_count_digits<uint32_t>();
|
||||
test_count_digits<uint64_t>();
|
||||
}
|
||||
|
||||
TEST(UtilTest, WriteFallbackUIntPtr) {
|
||||
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");
|
||||
}
|
||||
|
2704
test/format-test.cc
2704
test/format-test.cc
File diff suppressed because it is too large
Load Diff
3
test/fuzzing/.gitignore
vendored
Normal file
3
test/fuzzing/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# ignore artifacts from the build.sh script
|
||||
build-*/
|
||||
|
38
test/fuzzing/CMakeLists.txt
Normal file
38
test/fuzzing/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# 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)
|
||||
|
||||
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
|
||||
# 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)
|
||||
if (FMT_FUZZ_LINKMAIN)
|
||||
target_sources(${name} PRIVATE main.cpp)
|
||||
endif ()
|
||||
target_link_libraries(${name} PRIVATE fmt)
|
||||
if (FMT_FUZZ_LDFLAGS)
|
||||
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
|
||||
endif ()
|
||||
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
|
||||
endmacro ()
|
||||
|
||||
foreach (X IN ITEMS ${SOURCES})
|
||||
implement_fuzzer(${X})
|
||||
endforeach ()
|
48
test/fuzzing/README.md
Normal file
48
test/fuzzing/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
Windows (using clang>=8) or on Mac, but the script will probably not work out of
|
||||
the box.
|
||||
|
||||
Something along
|
||||
```sh
|
||||
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 --build .
|
||||
```
|
||||
should work to build the fuzzers for all platforms which clang supports.
|
||||
|
||||
Execute a fuzzer with for instance
|
||||
```sh
|
||||
cd build
|
||||
export UBSAN_OPTIONS=halt_on_error=1
|
||||
mkdir out_chrono
|
||||
bin/fuzzer_chrono_duration out_chrono
|
||||
```
|
110
test/fuzzing/build.sh
Executable file
110
test/fuzzing/build.sh
Executable file
@ -0,0 +1,110 @@
|
||||
#!/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)
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2019 Paul Dreik
|
||||
#
|
||||
# License: see LICENSE.rst in the fmt root directory
|
||||
|
||||
set -e
|
||||
me=$(basename $0)
|
||||
root=$(readlink -f "$(dirname "$0")/../..")
|
||||
|
||||
|
||||
echo $me: root=$root
|
||||
|
||||
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
|
||||
|
||||
#for performance analysis of the fuzzers
|
||||
builddir=$here/build-fuzzers-perfanalysis
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL -g" cmake \
|
||||
$CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=On \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
#builds the fuzzers as oss-fuzz does
|
||||
builddir=$here/build-fuzzers-ossfuzz
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
|
||||
#builds fuzzers for local fuzzing with libfuzzer with asan+usan
|
||||
builddir=$here/build-fuzzers-libfuzzer
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
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
|
||||
builddir=$here/build-fuzzers-fast
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="clang++" \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=Off \
|
||||
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
|
||||
#builds fuzzers for local fuzzing with afl
|
||||
builddir=$here/build-fuzzers-afl
|
||||
mkdir -p $builddir
|
||||
cd $builddir
|
||||
CXX="afl-g++" \
|
||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=address,undefined" \
|
||||
cmake $CMAKEFLAGSALL \
|
||||
-DFMT_FUZZ_LINKMAIN=On
|
||||
|
||||
cmake --build $builddir
|
||||
|
||||
|
||||
echo $me: all good
|
||||
|
152
test/fuzzing/chrono_duration.cpp
Normal file
152
test/fuzzing/chrono_duration.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
// 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;
|
||||
}
|
67
test/fuzzing/fuzzer_common.h
Normal file
67
test/fuzzing/fuzzer_common.h
Normal file
@ -0,0 +1,67 @@
|
||||
#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
|
21
test/fuzzing/main.cpp
Normal file
21
test/fuzzing/main.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#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());
|
||||
}
|
||||
}
|
128
test/fuzzing/named_arg.cpp
Normal file
128
test/fuzzing/named_arg.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
// 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;
|
||||
}
|
131
test/fuzzing/one_arg.cpp
Normal file
131
test/fuzzing/one_arg.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
// 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;
|
||||
}
|
116
test/fuzzing/sprintf.cpp
Normal file
116
test/fuzzing/sprintf.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
// 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;
|
||||
}
|
112
test/fuzzing/two_args.cpp
Normal file
112
test/fuzzing/two_args.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
// 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;
|
||||
}
|
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