mirror of
https://github.com/fmtlib/fmt.git
synced 2025-06-25 09:21:41 +02:00
Compare commits
1052 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
19
.gitignore
vendored
19
.gitignore
vendored
@ -1,18 +1,33 @@
|
||||
.vscode/
|
||||
|
||||
*.iml
|
||||
.idea/
|
||||
.externalNativeBuild/
|
||||
.gradle/
|
||||
gradle/
|
||||
gradlew*
|
||||
local.properties
|
||||
build/
|
||||
|
||||
bin/
|
||||
/_CPack_Packages
|
||||
/CMakeScripts
|
||||
/doc/doxyxml
|
||||
/doc/html
|
||||
/doc/virtualenv
|
||||
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
|
||||
|
131
.travis.yml
131
.travis.yml
@ -1,24 +1,129 @@
|
||||
language: cpp
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
os: linux
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
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
|
||||
# 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
|
||||
- env: COMPILER=clang++-6.0 BUILD=Debug STANDARD=14
|
||||
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
|
||||
# g++ 4.4 on Linux with C++11
|
||||
- env: COMPILER=g++-4.4 BUILD=Debug STANDARD=11
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
packages:
|
||||
- g++-4.4
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
# Android
|
||||
- language: android
|
||||
android:
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- android-21
|
||||
- sys-img-armeabi-v7a-android-21
|
||||
env:
|
||||
- ANDROID=true
|
||||
before_install:
|
||||
- git submodule update --init --recursive
|
||||
- sudo apt-get install wget unzip tree
|
||||
install:
|
||||
# Accept SDK Licenses + Install NDK
|
||||
- yes | sdkmanager --update > /dev/null 2>&1
|
||||
- sdkmanager ndk-bundle > /dev/null 2>&1
|
||||
# Download Gradle 4.3.1
|
||||
- wget https://services.gradle.org/distributions/gradle-4.3.1-bin.zip
|
||||
- mkdir -p gradle
|
||||
- unzip -q -d ./gradle gradle-4.3.1-bin.zip
|
||||
- export GRADLE=${TRAVIS_BUILD_DIR}/gradle/gradle-4.3.1/bin/gradle
|
||||
before_script:
|
||||
- bash $GRADLE --version
|
||||
- cd ./support
|
||||
script:
|
||||
- bash $GRADLE clean assemble
|
||||
after_success:
|
||||
- cd ${TRAVIS_BUILD_DIR}
|
||||
- tree ./libs
|
||||
allow_failures:
|
||||
# Errors
|
||||
- env: COMPILER=g++-4.4 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
|
||||
|
284
CMakeLists.txt
284
CMakeLists.txt
@ -1,64 +1,124 @@
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
# 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)
|
||||
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()
|
||||
|
||||
# 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.")
|
||||
join(doc "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING ${doc})
|
||||
endif ()
|
||||
|
||||
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})
|
||||
|
||||
project(FORMAT)
|
||||
project(FMT)
|
||||
|
||||
# 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 ()
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
|
||||
if (CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
||||
-Wold-style-cast -Wfloat-equal -Wlogical-op -Wundef
|
||||
-Wredundant-decls -Wshadow -Wwrite-strings -Wpointer-arith
|
||||
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
|
||||
-Wcast-align -Wnon-virtual-dtor
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Wmissing-declarations -Woverloaded-virtual
|
||||
-Wno-ctor-dtor-privacy -Wno-dangling-else -Wno-float-equal
|
||||
-Wno-format-nonliteral -Wno-sign-conversion -Wno-shadow)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept)
|
||||
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 -Weverything -Wpedantic
|
||||
-Wno-weak-vtables -Wno-padded -Wno-gnu-statement-expression
|
||||
-Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-reserved-id-macro
|
||||
-Wno-global-constructors -Wno-disabled-macro-expansion
|
||||
-Wno-switch-enum -Wno-documentation-unknown-command
|
||||
-Wno-gnu-string-literal-operator-template -Wno-unused-member-function
|
||||
-Wno-format-nonliteral -Wno-missing-noreturn -Wno-undefined-func-template
|
||||
-Wno-shadow -Wno-sign-conversion -Wno-used-but-marked-unused
|
||||
-Wno-covered-switch-default -Wno-missing-variable-declarations
|
||||
-Wno-double-promotion)
|
||||
|
||||
set(WERROR_FLAG -Werror)
|
||||
|
||||
check_cxx_compiler_flag(-Wno-zero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||
if (HAS_NULLPTR_WARNING)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-zero-as-null-pointer-constant)
|
||||
endif ()
|
||||
|
||||
check_cxx_compiler_flag(-Wno-gnu-string-literal-operator-template HAS_GNU_UDL_WARNING)
|
||||
if (HAS_GNU_UDL_WARNING)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-gnu-string-literal-operator-template)
|
||||
endif ()
|
||||
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)
|
||||
@ -72,58 +132,113 @@ if (CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
|
||||
endif ()
|
||||
|
||||
set(FMT_SOURCES format.cc format.h)
|
||||
|
||||
include(CheckSymbolExists)
|
||||
if (WIN32)
|
||||
check_symbol_exists(open io.h HAVE_OPEN)
|
||||
else ()
|
||||
check_symbol_exists(open fcntl.h HAVE_OPEN)
|
||||
endif ()
|
||||
|
||||
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 core.h format.h format-inl.h ostream.h printf.h time.h
|
||||
ranges.h)
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
if (HAVE_OPEN)
|
||||
add_definitions(-DFMT_USE_FILE_DESCRIPTORS=1)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} posix.cc posix.h)
|
||||
add_headers(FMT_HEADERS posix.h)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} src/posix.cc)
|
||||
endif ()
|
||||
|
||||
if (CPP11_FLAG)
|
||||
set(CMAKE_REQUIRED_FLAGS ${CPP11_FLAG})
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
||||
endif ()
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
|
||||
if (BIICODE)
|
||||
include(support/cmake/biicode.cmake)
|
||||
return()
|
||||
target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX d)
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (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(fmt -Wl,--as-needed)
|
||||
endif ()
|
||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||
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")
|
||||
endif ()
|
||||
add_library(fmt-header-only INTERFACE)
|
||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
|
||||
# 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)
|
||||
endif ()
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
|
||||
set_target_properties(cppformat
|
||||
PROPERTIES COMPILE_FLAGS "${FMT_EXTRA_COMPILE_FLAGS}")
|
||||
target_include_directories(fmt-header-only INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
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}.")
|
||||
# Install targets.
|
||||
if (FMT_INSTALL)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
set(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||
"Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||
set(targets_export_name fmt-targets)
|
||||
|
||||
set (INSTALL_TARGETS fmt)
|
||||
if (TARGET fmt-header-only)
|
||||
set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only)
|
||||
endif ()
|
||||
|
||||
set(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
set(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING
|
||||
"Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
${version_config}
|
||||
VERSION ${FMT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
configure_package_config_file(
|
||||
${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
|
||||
${project_config}
|
||||
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||
# 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}
|
||||
DESTINATION ${FMT_LIB_DIR})
|
||||
install(FILES ${FMT_HEADERS} DESTINATION ${FMT_INC_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,12 +249,10 @@ if (FMT_TEST)
|
||||
add_subdirectory(test)
|
||||
endif ()
|
||||
|
||||
set_target_properties(cppformat PROPERTIES
|
||||
VERSION ${CPPFORMAT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR})
|
||||
|
||||
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)
|
||||
file (STRINGS ${gitignore} lines)
|
||||
LIST(REMOVE_ITEM lines /doc/html)
|
||||
foreach (line ${lines})
|
||||
string(REPLACE "." "[.]" line "${line}")
|
||||
@ -151,15 +264,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 ()
|
||||
|
12
CONTRIBUTING.rst
Normal file
12
CONTRIBUTING.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Contributing to fmt
|
||||
===================
|
||||
|
||||
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!
|
1215
ChangeLog.rst
1215
ChangeLog.rst
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012 - 2015, Victor Zverovich
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
|
||||
All rights reserved.
|
||||
|
||||
|
281
README.rst
281
README.rst
@ -1,62 +1,62 @@
|
||||
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://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
.. 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
|
||||
:alt: Join the chat at https://gitter.im/fmtlib/fmt
|
||||
:target: https://gitter.im/fmtlib/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.
|
||||
**{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 <http://cppformat.github.io/latest/>`_
|
||||
`Documentation <http://fmtlib.net/latest/>`__
|
||||
|
||||
This is a development branch that implements the C++ standards proposal `P0645
|
||||
Text Formatting <http://fmtlib.net/Text%20Formatting.html>`__.
|
||||
Released versions are available from the `Releases page
|
||||
<https://github.com/fmtlib/fmt/releases>`__.
|
||||
|
||||
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.
|
||||
* Replacement-based `format API <http://fmtlib.net/dev/api.html>`_ with
|
||||
positional arguments for localization.
|
||||
* `Format string syntax <http://fmtlib.net/dev/syntax.html>`_ similar to the one
|
||||
of `str.format <https://docs.python.org/2/library/stdtypes.html#str.format>`_
|
||||
in Python.
|
||||
* Safe `printf implementation
|
||||
<http://cppformat.github.io/latest/reference.html#printf-formatting-functions>`_
|
||||
including the POSIX extension for positional arguments.
|
||||
<http://fmtlib.net/latest/api.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
|
||||
* 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 the
|
||||
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`_.
|
||||
* 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>`_.
|
||||
* 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.
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||
* `Portability <http://fmtlib.net/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).
|
||||
(``-Wall -Wextra -pedantic``).
|
||||
* 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 <http://fmtlib.net/latest/>`_ for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
@ -75,20 +75,45 @@ Arguments can be accessed by position and arguments' indices can be repeated:
|
||||
std::string s = fmt::format("{0}{1}{0}", "abra", "cad");
|
||||
// s == "abracadabra"
|
||||
|
||||
C++ Format can be used as a safe portable replacement for ``itoa``:
|
||||
Format strings can be checked at compile time:
|
||||
|
||||
.. 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()
|
||||
// test.cc
|
||||
#include <fmt/format.h>
|
||||
std::string s = format(fmt("{2}"), 42);
|
||||
|
||||
.. code::
|
||||
|
||||
$ c++ -Iinclude -std=c++14 test.cc
|
||||
...
|
||||
test.cc:3:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here
|
||||
std::string s = format(fmt("{2}"), 42);
|
||||
^
|
||||
include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression
|
||||
ErrorHandler::on_error(message);
|
||||
^
|
||||
include/fmt/format.h:2226:16: note: in call to '&checker.context_->on_error(&"argument index out of range"[0])'
|
||||
context_.on_error("argument index out of range");
|
||||
^
|
||||
|
||||
{fmt} can be used 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 using to_string(buf) or buf.data()
|
||||
|
||||
An object of any user-defined type for which there is an overloaded
|
||||
:code:`std::ostream` insertion operator (``operator<<``) can be formatted:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include "fmt/ostream.h"
|
||||
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
@ -102,27 +127,27 @@ An object of any user-defined type for which there is an overloaded
|
||||
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:
|
||||
You can create your own functions similar to `format
|
||||
<http://fmtlib.net/latest/api.html#format>`_ and
|
||||
`print <http://fmtlib.net/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.
|
||||
Note that ``vreport_error`` is not parameterized on argument types which can
|
||||
improve compile times and reduce code size compared to fully parameterized version.
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
@ -131,29 +156,58 @@ Projects using this library
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
|
||||
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft operations suite
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/HarpyWar/pvpgn>`_:
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater vehicle
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
* `KBEngine <http://www.kbengine.org/>`_: An open-source MMOG server engine
|
||||
* `KBEngine <http://kbengine.org/>`_: An open-source MMOG server engine
|
||||
|
||||
* `Keypirinha <http://keypirinha.com/>`_: A semantic launcher for Windows
|
||||
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
|
||||
|
||||
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
|
||||
|
||||
* `Drake <http://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
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `OpenSpace <http://openspaceproject.com/>`_: An open-source astrovisualization
|
||||
framework
|
||||
|
||||
* `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
|
||||
* `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/>`_:
|
||||
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:
|
||||
Small crossplatform 2D graphic engine
|
||||
|
||||
* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `Scylla <http://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
|
||||
@ -166,7 +220,7 @@ Projects using this library
|
||||
|
||||
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>`_.
|
||||
`issue <https://github.com/fmtlib/fmt/issues>`_.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
@ -188,7 +242,7 @@ 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>`_
|
||||
`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.
|
||||
|
||||
@ -277,23 +331,24 @@ The following speed tests results were generated by building
|
||||
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>`_.
|
||||
<https://github.com/fmtlib/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.35
|
||||
libc++ std::ostream 3.42
|
||||
fmt 534bff7 fmt::print 1.56
|
||||
tinyformat 2.0.1 tfm::printf 3.73
|
||||
Boost Format 1.54 boost::format 8.44
|
||||
Folly Format folly::format 2.54
|
||||
================= ============= ===========
|
||||
|
||||
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
|
||||
Performance of fmt 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.
|
||||
|
||||
@ -301,43 +356,50 @@ 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.7 29 26
|
||||
printf+string 18.4 29 26
|
||||
IOStreams 34.6 59 55
|
||||
fmt 22.0 37 34
|
||||
tinyformat 51.8 103 97
|
||||
Boost Format 120.5 762 739
|
||||
Folly Format 158.7 102 87
|
||||
============= =============== ==================== ==================
|
||||
|
||||
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
|
||||
has by far 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.4 33 30
|
||||
printf+string 18.5 33 30
|
||||
IOStreams 31.9 56 52
|
||||
fmt 20.9 56 51
|
||||
tinyformat 38.9 88 82
|
||||
Boost Format 64.8 366 304
|
||||
Folly Format 113.5 442 428
|
||||
============= =============== ==================== ==================
|
||||
|
||||
``libc``, ``libstdc++`` and ``libformat`` are all linked as shared
|
||||
``libc``, ``lib(std)c++`` and ``libfmt`` 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.
|
||||
@ -348,14 +410,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
|
||||
__ http://fmtlib.net/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 .
|
||||
|
||||
@ -370,27 +432,30 @@ or the bloat test::
|
||||
License
|
||||
-------
|
||||
|
||||
C++ Format is distributed under the BSD `license
|
||||
<https://github.com/cppformat/cppformat/blob/master/LICENSE.rst>`_.
|
||||
fmt is distributed under the BSD `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||
|
||||
The `Format String Syntax
|
||||
<http://cppformat.github.io/latest/syntax.html>`_
|
||||
<http://fmtlib.net/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>`_
|
||||
documentation <https://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.
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
`API changes/compatibility report <http://upstream-tracker.org/versions/cppformat.html>`_
|
||||
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||
It only applies if you distribute the documentation of fmt.
|
||||
|
||||
Acknowledgments
|
||||
---------------
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
@ -5,7 +5,8 @@ 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 build.py conf.py _templates/layout.html)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
||||
DESTINATION share/doc/cppformat)
|
||||
DESTINATION share/doc/fmt OPTIONAL)
|
||||
|
62
doc/_templates/layout.html
vendored
62
doc/_templates/layout.html
vendored
@ -1,25 +1,28 @@
|
||||
{% 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>
|
||||
(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)
|
||||
(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('create', 'UA-20116650-4', 'fmtlib.net');
|
||||
ga('send', 'pageview');
|
||||
</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 +39,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="http://fmtlib.net/{{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 +80,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">Small, safe and fast 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 +115,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 %}
|
||||
|
321
doc/api.rst
321
doc/api.rst
@ -4,126 +4,321 @@
|
||||
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, output iterator and user-defined type support
|
||||
* :ref:`fmt/time.h <time-api>`: date and time formatting
|
||||
* :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_`` or ``fmt``.
|
||||
|
||||
.. _core-api:
|
||||
|
||||
Core API
|
||||
========
|
||||
|
||||
``fmt/core.h`` defines the core API which provides argument handling facilities
|
||||
and a lightweight subset of formatting functions.
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>`
|
||||
imilar 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.
|
||||
|
||||
*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(string_view, const Args&...)
|
||||
.. doxygenfunction:: vformat(string_view, format_args)
|
||||
|
||||
.. _print:
|
||||
|
||||
.. doxygenfunction:: print(CStringRef, ArgList)
|
||||
.. doxygenfunction:: print(string_view, const Args&...)
|
||||
.. doxygenfunction:: vprint(string_view, format_args)
|
||||
|
||||
.. doxygenfunction:: print(std::FILE *, CStringRef, ArgList)
|
||||
.. doxygenfunction:: print(std::FILE *, string_view, const Args&...)
|
||||
.. doxygenfunction:: vprint(std::FILE *, string_view, format_args)
|
||||
|
||||
.. doxygenfunction:: print(std::ostream&, CStringRef, ArgList)
|
||||
.. doxygenfunction:: print(std::FILE *, wstring_view, const Args&...)
|
||||
.. doxygenfunction:: vprint(std::FILE *, wstring_view, wformat_args)
|
||||
|
||||
Printf formatting functions
|
||||
===========================
|
||||
Named arguments
|
||||
---------------
|
||||
|
||||
The following functions use `printf format string syntax
|
||||
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||
a POSIX extension for positional arguments.
|
||||
.. doxygenfunction:: fmt::arg(string_view, const T&)
|
||||
|
||||
.. 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::basic_format_args
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicArrayWriter
|
||||
.. doxygenstruct:: fmt::format_args
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_arg
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: bin
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
.. doxygenfunction:: oct
|
||||
.. doxygenclass:: fmt::basic_string_view
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: hex
|
||||
.. doxygentypedef:: fmt::string_view
|
||||
.. doxygentypedef:: fmt::wstring_view
|
||||
|
||||
.. doxygenfunction:: hexu
|
||||
.. _format-api:
|
||||
|
||||
.. doxygenfunction:: pad(int, unsigned, Char)
|
||||
Format API
|
||||
==========
|
||||
|
||||
Utilities
|
||||
=========
|
||||
``fmt/format.h`` defines the full format API providing compile-time format
|
||||
string checks, output iterator and user-defined type support.
|
||||
|
||||
.. doxygenfunction:: fmt::arg(StringRef, const T&)
|
||||
Compile-time format string checks
|
||||
---------------------------------
|
||||
|
||||
.. doxygendefine:: fmt
|
||||
|
||||
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; };
|
||||
|
||||
namespace fmt {
|
||||
template <>
|
||||
struct formatter<point> {
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const point &p, FormatContext &ctx) {
|
||||
return format_to(ctx.begin(), "({:.1f}, {:.1f})", 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("{}", p);
|
||||
// s == "(1.0, 2.0)"
|
||||
|
||||
In the example above the ``formatter<point>::parse`` function ignores the
|
||||
contents of the format string referred to by ``ctx.begin()`` so the object will
|
||||
always be formatted in the same way. See ``formatter<tm>::parse`` in
|
||||
:file:`fmt/time.h` for an advanced example of how to parse the format string and
|
||||
customize the formatted output.
|
||||
|
||||
This section shows how to define a custom format function for a user-defined
|
||||
type. The next section describes how to get ``fmt`` to use a conventional stream
|
||||
output ``operator<<`` when one is defined for a user-defined type.
|
||||
|
||||
Output iterator support
|
||||
-----------------------
|
||||
|
||||
.. doxygenfunction:: fmt::format_to(OutputIt, string_view, const Args&...)
|
||||
.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, string_view, const Args&...)
|
||||
.. doxygenstruct:: fmt::format_to_n_result
|
||||
:members:
|
||||
|
||||
Literal-based API
|
||||
-----------------
|
||||
|
||||
The following user-defined literals are defined in ``fmt/format.h``.
|
||||
|
||||
.. doxygenfunction:: operator""_format(const char *, std::size_t)
|
||||
|
||||
.. doxygenfunction:: operator""_a(const char *, std::size_t)
|
||||
|
||||
.. doxygendefine:: FMT_CAPTURE
|
||||
Utilities
|
||||
---------
|
||||
|
||||
.. doxygendefine:: FMT_VARIADIC
|
||||
.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...)
|
||||
|
||||
.. doxygenclass:: fmt::ArgList
|
||||
:members:
|
||||
.. doxygenfunction:: fmt::to_string(const T&)
|
||||
|
||||
.. doxygenclass:: fmt::BasicStringRef
|
||||
:members:
|
||||
.. doxygenfunction:: fmt::to_wstring(const T&)
|
||||
|
||||
.. doxygenclass:: fmt::BasicCStringRef
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::Buffer
|
||||
.. doxygenclass:: fmt::basic_memory_buffer
|
||||
:protected-members:
|
||||
:members:
|
||||
|
||||
System Errors
|
||||
=============
|
||||
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
|
||||
=================
|
||||
-----------------
|
||||
|
||||
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.
|
||||
|
||||
Custom formatting of built-in types
|
||||
-----------------------------------
|
||||
|
||||
It is possible to change the way arguments are formatted by providing a
|
||||
custom argument formatter class::
|
||||
|
||||
using arg_formatter =
|
||||
fmt::arg_formatter<fmt::back_insert_range<fmt::internal::buffer>>;
|
||||
|
||||
// A custom argument formatter that formats negative integers as unsigned
|
||||
// with the ``x`` format specifier.
|
||||
class custom_arg_formatter : public arg_formatter {
|
||||
public:
|
||||
custom_arg_formatter(fmt::format_context &ctx, fmt::format_specs &spec)
|
||||
: arg_formatter(ctx, spec) {}
|
||||
|
||||
using arg_formatter::operator();
|
||||
|
||||
void operator()(int value) {
|
||||
if (spec().type() == 'x')
|
||||
(*this)(static_cast<unsigned>(value)); // convert to unsigned and format
|
||||
else
|
||||
arg_formatter::operator()(value);
|
||||
}
|
||||
};
|
||||
|
||||
std::string custom_vformat(fmt::string_view format_str, fmt::format_args args) {
|
||||
fmt::memory_buffer buffer;
|
||||
// Pass custom argument formatter as a template arg to vformat_to.
|
||||
fmt::vformat_to<custom_arg_formatter>(buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
inline std::string custom_format(
|
||||
fmt::string_view format_str, const Args &... args) {
|
||||
return custom_vformat(format_str, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
std::string s = custom_format("{:x}", -42); // s == "ffffffd6"
|
||||
|
||||
.. doxygenclass:: fmt::arg_formatter
|
||||
:members:
|
||||
|
||||
.. _time-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/time.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}.", *std::localtime(&t));
|
||||
|
||||
The format string syntax is described in the documentation of
|
||||
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
|
||||
|
||||
.. _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::ostream&, string_view, const 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(string_view, const Args&...)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::FILE *, string_view, const Args&...)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::ostream&, string_view, const Args&...)
|
||||
|
||||
.. doxygenfunction:: sprintf(string_view, const Args&...)
|
||||
|
@ -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 %}
|
||||
|
86
doc/build.py
86
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']
|
||||
|
||||
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,69 @@ 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}/format.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_API= \
|
||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||
"FMT_END_NAMESPACE=}}"
|
||||
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;
|
||||
}
|
159
doc/index.rst
159
doc/index.rst
@ -1,22 +1,21 @@
|
||||
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** (formerly cppformat) is an open-source formatting library.
|
||||
It can be used as a fast and safe alternative to printf and 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 boost::format and loki::SPrintf, and neither felt
|
||||
like the right answer. This does.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
.. _format-api:
|
||||
.. _format-api-intro:
|
||||
|
||||
Format API
|
||||
----------
|
||||
@ -24,22 +23,22 @@ 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>`_
|
||||
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:
|
||||
|
||||
@ -54,11 +53,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++
|
||||
@ -93,38 +87,31 @@ 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
|
||||
tim. 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("The answer is {:d}"), "forty-two");
|
||||
|
||||
reports a compile-time error for the same reason on compilers that support
|
||||
relaxed ``constexpr``.
|
||||
|
||||
The following code
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -138,44 +125,64 @@ 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
|
||||
|
||||
* 32-bit (i386) GNU/Linux with GCC 4.4.3, 4.6.3
|
||||
These are available since GCC 4.4, Clang 2.9 and MSVC 18.0 (2013). 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 in this case. For example,
|
||||
|
||||
.. code::
|
||||
|
||||
@ -188,16 +195,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 BSD `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>
|
||||
|
@ -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.
|
||||
|
||||
@ -80,8 +81,8 @@ The general form of a *standard format specifier* is:
|
||||
sign: "+" | "-" | " "
|
||||
width: `integer` | "{" `arg_id` "}"
|
||||
precision: `integer` | "{" `arg_id` "}"
|
||||
type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "n" | "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
|
||||
@ -216,6 +217,10 @@ The available integer presentation types are:
|
||||
| | ``'#'`` option with this type adds the prefix ``"0X"`` |
|
||||
| | to the output value. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'n'`` | Number. 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 +235,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. |
|
||||
@ -262,6 +267,8 @@ The available presentation types for floating-point values are:
|
||||
| none | The same as ``'g'``. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
Floating-point formatting is locale-dependent.
|
||||
|
||||
.. ifconfig:: False
|
||||
|
||||
+---------+----------------------------------------------------------+
|
||||
@ -331,6 +338,16 @@ Aligning the text and specifying a width::
|
||||
format("{:*^30}", "centered"); // use '*' as a fill char
|
||||
// Result: "***********centered***********"
|
||||
|
||||
Dynamic width::
|
||||
|
||||
format("{:<{}}", "left aligned", 30);
|
||||
// Result: "left aligned "
|
||||
|
||||
Dynamic precision::
|
||||
|
||||
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
|
||||
@ -348,6 +365,11 @@ 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);
|
||||
// Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010"
|
||||
|
||||
Padded hex byte with prefix and always prints both hex characters::
|
||||
|
||||
format("{:#04x}", 0);
|
||||
// Result: "0x00"
|
||||
|
||||
.. ifconfig:: False
|
||||
|
||||
Using the comma as a thousands separator::
|
||||
@ -355,13 +377,6 @@ Replacing ``%x`` and ``%o`` and converting the value to different bases::
|
||||
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
|
||||
|
@ -2,9 +2,9 @@
|
||||
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:`format.h` and :file:`format.cc` 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
|
||||
@ -19,28 +19,32 @@ before other includes in :file:`format.cc`.
|
||||
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 <path/to/fmt> # 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.
|
||||
|
||||
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:`FORMAT.sln`
|
||||
file and several :file:`.vcproj` files will be created. You can then build them
|
||||
using Visual Studio or msbuild.
|
||||
@ -54,6 +58,26 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
|
||||
|
||||
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
||||
|
||||
Header-only 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 .``.
|
||||
|
||||
Settting up your target to use a header-only version of ``fmt`` is equaly easy::
|
||||
|
||||
target_link_libraries(<your-target> PRIVATE fmt-header-only)
|
||||
|
||||
Building the documentation
|
||||
==========================
|
||||
|
||||
@ -62,29 +86,33 @@ 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``.
|
||||
|
||||
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
|
||||
|
1331
include/fmt/core.h
Normal file
1331
include/fmt/core.h
Normal file
File diff suppressed because it is too large
Load Diff
564
include/fmt/format-inl.h
Normal file
564
include/fmt/format-inl.h
Normal file
@ -0,0 +1,564 @@
|
||||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_FORMAT_INL_H_
|
||||
#define FMT_FORMAT_INL_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef> // for std::ptrdiff_t
|
||||
#include <locale>
|
||||
|
||||
#if defined(_WIN32) && defined(__MINGW32__)
|
||||
# include <cstring>
|
||||
#endif
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
|
||||
# include <windows.h>
|
||||
# else
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
# undef NOMINMAX
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_EXCEPTIONS
|
||||
# define FMT_TRY try
|
||||
# define FMT_CATCH(x) catch (x)
|
||||
#else
|
||||
# define FMT_TRY if (true)
|
||||
# define FMT_CATCH(x) if (false)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4127) // conditional expression is constant
|
||||
# pragma warning(disable: 4702) // unreachable code
|
||||
// Disable deprecation warning for strerror. The latter is not called but
|
||||
// MSVC fails to detect it.
|
||||
# pragma warning(disable: 4996)
|
||||
#endif
|
||||
|
||||
// Dummy implementations of strerror_r and strerror_s called if corresponding
|
||||
// system functions are not available.
|
||||
inline fmt::internal::null<> strerror_r(int, char *, ...) {
|
||||
return fmt::internal::null<>();
|
||||
}
|
||||
inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) {
|
||||
return fmt::internal::null<>();
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define FMT_SNPRINTF snprintf
|
||||
#else // _MSC_VER
|
||||
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
# define FMT_SNPRINTF fmt_snprintf
|
||||
#endif // _MSC_VER
|
||||
|
||||
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
# define FMT_SWPRINTF snwprintf
|
||||
#else
|
||||
# define FMT_SWPRINTF swprintf
|
||||
#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
|
||||
typedef void (*FormatFunc)(internal::buffer &, int, string_view);
|
||||
|
||||
// Portable thread-safe version of strerror.
|
||||
// Sets buffer to point to a string describing the error code.
|
||||
// This can be either a pointer to a string stored in buffer,
|
||||
// or a pointer to some static immutable string.
|
||||
// Returns one of the following values:
|
||||
// 0 - success
|
||||
// ERANGE - buffer is not large enough to store the error message
|
||||
// other - failure
|
||||
// Buffer should be at least of size 1.
|
||||
int safe_strerror(
|
||||
int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {
|
||||
FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer");
|
||||
|
||||
class dispatcher {
|
||||
private:
|
||||
int error_code_;
|
||||
char *&buffer_;
|
||||
std::size_t buffer_size_;
|
||||
|
||||
// A noop assignment operator to avoid bogus warnings.
|
||||
void operator=(const dispatcher &) {}
|
||||
|
||||
// Handle the result of XSI-compliant version of strerror_r.
|
||||
int handle(int result) {
|
||||
// glibc versions before 2.13 return result in errno.
|
||||
return result == -1 ? errno : result;
|
||||
}
|
||||
|
||||
// Handle the result of GNU-specific version of strerror_r.
|
||||
int handle(char *message) {
|
||||
// If the buffer is full then the message is probably truncated.
|
||||
if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
|
||||
return ERANGE;
|
||||
buffer_ = message;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle the case when strerror_r is not available.
|
||||
int handle(internal::null<>) {
|
||||
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
|
||||
}
|
||||
|
||||
// Fallback to strerror_s when strerror_r is not available.
|
||||
int fallback(int result) {
|
||||
// If the buffer is full then the message is probably truncated.
|
||||
return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?
|
||||
ERANGE : result;
|
||||
}
|
||||
|
||||
// Fallback to strerror if strerror_r and strerror_s are not available.
|
||||
int fallback(internal::null<>) {
|
||||
errno = 0;
|
||||
buffer_ = strerror(error_code_);
|
||||
return errno;
|
||||
}
|
||||
|
||||
public:
|
||||
dispatcher(int err_code, char *&buf, std::size_t buf_size)
|
||||
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
|
||||
|
||||
int run() {
|
||||
return handle(strerror_r(error_code_, buffer_, buffer_size_));
|
||||
}
|
||||
};
|
||||
return dispatcher(error_code, buffer, buffer_size).run();
|
||||
}
|
||||
|
||||
void format_error_code(internal::buffer &out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
// Report error code making sure that the output fits into
|
||||
// inline_buffer_size to avoid dynamic memory allocation and potential
|
||||
// bad_alloc.
|
||||
out.resize(0);
|
||||
static const char SEP[] = ": ";
|
||||
static const char ERROR_STR[] = "error ";
|
||||
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
|
||||
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
||||
typedef internal::int_traits<int>::main_type main_type;
|
||||
main_type abs_value = static_cast<main_type>(error_code);
|
||||
if (internal::is_negative(error_code)) {
|
||||
abs_value = 0 - abs_value;
|
||||
++error_code_size;
|
||||
}
|
||||
error_code_size += internal::count_digits(abs_value);
|
||||
writer w(out);
|
||||
if (message.size() <= inline_buffer_size - error_code_size) {
|
||||
w.write(message);
|
||||
w.write(SEP);
|
||||
}
|
||||
w.write(ERROR_STR);
|
||||
w.write(error_code);
|
||||
assert(out.size() <= inline_buffer_size);
|
||||
}
|
||||
|
||||
void report_error(FormatFunc func, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
memory_buffer full_message;
|
||||
func(full_message, error_code, message);
|
||||
// Use Writer::data instead of Writer::c_str to avoid potential memory
|
||||
// allocation.
|
||||
std::fwrite(full_message.data(), full_message.size(), 1, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class locale {
|
||||
private:
|
||||
std::locale locale_;
|
||||
|
||||
public:
|
||||
explicit locale(std::locale loc = std::locale()) : locale_(loc) {}
|
||||
std::locale get() { return locale_; }
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::thousands_sep(locale_provider *lp) {
|
||||
std::locale loc = lp ? lp->locale().get() : std::locale();
|
||||
return std::use_facet<std::numpunct<Char>>(loc).thousands_sep();
|
||||
}
|
||||
|
||||
FMT_FUNC void system_error::init(
|
||||
int err_code, string_view format_str, format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
format_system_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
template <typename T>
|
||||
int char_traits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format, int precision, T value) {
|
||||
return precision < 0 ?
|
||||
FMT_SNPRINTF(buffer, size, format, value) :
|
||||
FMT_SNPRINTF(buffer, size, format, precision, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int char_traits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format, int precision,
|
||||
T value) {
|
||||
return precision < 0 ?
|
||||
FMT_SWPRINTF(buffer, size, format, value) :
|
||||
FMT_SWPRINTF(buffer, size, format, precision, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char basic_data<T>::DIGITS[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
|
||||
#define FMT_POWERS_OF_10(factor) \
|
||||
factor * 10, \
|
||||
factor * 100, \
|
||||
factor * 1000, \
|
||||
factor * 10000, \
|
||||
factor * 100000, \
|
||||
factor * 1000000, \
|
||||
factor * 10000000, \
|
||||
factor * 100000000, \
|
||||
factor * 1000000000
|
||||
|
||||
template <typename T>
|
||||
const uint32_t basic_data<T>::POWERS_OF_10_32[] = {
|
||||
0, FMT_POWERS_OF_10(1)
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const uint64_t basic_data<T>::POWERS_OF_10_64[] = {
|
||||
0,
|
||||
FMT_POWERS_OF_10(1),
|
||||
FMT_POWERS_OF_10(1000000000ull),
|
||||
10000000000000000000ull
|
||||
};
|
||||
|
||||
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
|
||||
// These are generated by support/compute-powers.py.
|
||||
template <typename T>
|
||||
const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = {
|
||||
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, 0xcf42894a5dce35ea,
|
||||
0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f,
|
||||
0xbe5691ef416bd60c, 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
|
||||
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, 0xc21094364dfb5637,
|
||||
0x9096ea6f3848984f, 0xd77485cb25823ac7, 0xa086cfcd97bf97f4, 0xef340a98172aace5,
|
||||
0xb23867fb2a35b28e, 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
|
||||
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, 0xb5b5ada8aaff80b8,
|
||||
0x87625f056c7c4a8b, 0xc9bcff6034c13053, 0x964e858c91ba2655, 0xdff9772470297ebd,
|
||||
0xa6dfbd9fb8e5b88f, 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
|
||||
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, 0xaa242499697392d3,
|
||||
0xfd87b5f28300ca0e, 0xbce5086492111aeb, 0x8cbccc096f5088cc, 0xd1b71758e219652c,
|
||||
0x9c40000000000000, 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
|
||||
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, 0x9f4f2726179a2245,
|
||||
0xed63a231d4c4fb27, 0xb0de65388cc8ada8, 0x83c7088e1aab65db, 0xc45d1df942711d9a,
|
||||
0x924d692ca61be758, 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
|
||||
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, 0x952ab45cfa97a0b3,
|
||||
0xde469fbd99a05fe3, 0xa59bc234db398c25, 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece,
|
||||
0x88fcf317f22241e2, 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
|
||||
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, 0x8bab8eefb6409c1a,
|
||||
0xd01fef10a657842c, 0x9b10a4e5e9913129, 0xe7109bfba19c0c9d, 0xac2820d9623bf429,
|
||||
0x80444b5e7aa7cf85, 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
|
||||
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b
|
||||
};
|
||||
|
||||
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
|
||||
// to significands above.
|
||||
template <typename T>
|
||||
const int16_t basic_data<T>::POW10_EXPONENTS[] = {
|
||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
|
||||
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
|
||||
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
|
||||
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
|
||||
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
|
||||
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
|
||||
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
|
||||
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066
|
||||
};
|
||||
|
||||
template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m";
|
||||
template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m";
|
||||
|
||||
FMT_FUNC fp operator*(fp x, fp y) {
|
||||
// Multiply 32-bit parts of significands.
|
||||
uint64_t mask = (1ULL << 32) - 1;
|
||||
uint64_t a = x.f >> 32, b = x.f & mask;
|
||||
uint64_t c = y.f >> 32, d = y.f & mask;
|
||||
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
|
||||
// Compute mid 64-bit of result and round.
|
||||
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
|
||||
return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64);
|
||||
}
|
||||
|
||||
FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) {
|
||||
const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10)
|
||||
int index = static_cast<int>(std::ceil(
|
||||
(min_exponent + fp::significand_size - 1) * one_over_log2_10));
|
||||
// Decimal exponent of the first (smallest) cached power of 10.
|
||||
const int first_dec_exp = -348;
|
||||
// Difference between two consecutive decimal exponents in cached powers of 10.
|
||||
const int dec_exp_step = 8;
|
||||
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
|
||||
pow10_exponent = first_dec_exp + index * dec_exp_step;
|
||||
return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16";
|
||||
if (s.size() > INT_MAX)
|
||||
FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG));
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// MultiByteToWideChar does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);
|
||||
if (length == 0)
|
||||
FMT_THROW(windows_error(GetLastError(), ERROR_MSG));
|
||||
buffer_.resize(length + 1);
|
||||
length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);
|
||||
if (length == 0)
|
||||
FMT_THROW(windows_error(GetLastError(), ERROR_MSG));
|
||||
buffer_[length] = 0;
|
||||
}
|
||||
|
||||
FMT_FUNC internal::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"));
|
||||
}
|
||||
}
|
||||
|
||||
FMT_FUNC int internal::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, FMT_NULL, 0, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_FUNC void windows_error::init(
|
||||
int err_code, string_view format_str, format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::format_windows_error(
|
||||
internal::buffer &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,
|
||||
FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
system_message, static_cast<uint32_t>(buf.size()), FMT_NULL);
|
||||
if (result != 0) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(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);
|
||||
}
|
||||
|
||||
#endif // FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC void format_system_error(
|
||||
internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
memory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
for (;;) {
|
||||
char *system_message = &buf[0];
|
||||
int result = safe_strerror(error_code, system_message, buf.size());
|
||||
if (result == 0) {
|
||||
writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(system_message);
|
||||
return;
|
||||
}
|
||||
if (result != ERANGE)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
} FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void basic_fixed_buffer<Char>::grow(std::size_t) {
|
||||
FMT_THROW(std::runtime_error("buffer overflow"));
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::error_handler::on_error(const char *message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
|
||||
FMT_FUNC void report_system_error(
|
||||
int error_code, fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
FMT_FUNC void report_windows_error(
|
||||
int error_code, fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(internal::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) {
|
||||
memory_buffer buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
std::fwrite(buffer.data(), 1, buffer.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(string_view format_str, format_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(wstring_view format_str, wformat_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
}
|
||||
|
||||
#ifndef FMT_EXTENDED_COLORS
|
||||
FMT_FUNC void vprint_colored(color c, string_view format, format_args args) {
|
||||
char escape[] = "\x1b[30m";
|
||||
escape[3] = static_cast<char>('0' + c);
|
||||
std::fputs(escape, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint_colored(color c, wstring_view format, wformat_args args) {
|
||||
wchar_t escape[] = L"\x1b[30m";
|
||||
escape[3] = static_cast<wchar_t>('0' + c);
|
||||
std::fputws(escape, stdout);
|
||||
vprint(format, args);
|
||||
std::fputws(internal::data::WRESET_COLOR, stdout);
|
||||
}
|
||||
#else
|
||||
namespace internal {
|
||||
FMT_CONSTEXPR void to_esc(uint8_t c, char out[], int offset) {
|
||||
out[offset + 0] = static_cast<char>('0' + c / 100);
|
||||
out[offset + 1] = static_cast<char>('0' + c / 10 % 10);
|
||||
out[offset + 2] = static_cast<char>('0' + c % 10);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) {
|
||||
char escape_fd[] = "\x1b[38;2;000;000;000m";
|
||||
internal::to_esc(fd.r, escape_fd, 7);
|
||||
internal::to_esc(fd.g, escape_fd, 11);
|
||||
internal::to_esc(fd.b, escape_fd, 15);
|
||||
|
||||
std::fputs(escape_fd, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) {
|
||||
char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color
|
||||
char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color
|
||||
internal::to_esc(fd.r, escape_fd, 7);
|
||||
internal::to_esc(fd.g, escape_fd, 11);
|
||||
internal::to_esc(fd.b, escape_fd, 15);
|
||||
|
||||
internal::to_esc(bg.r, escape_bg, 7);
|
||||
internal::to_esc(bg.g, escape_bg, 11);
|
||||
internal::to_esc(bg.b, escape_bg, 15);
|
||||
|
||||
std::fputs(escape_fd, stdout);
|
||||
std::fputs(escape_bg, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC locale locale_provider::locale() { return fmt::locale(); }
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif // FMT_FORMAT_INL_H_
|
3937
include/fmt/format.h
Normal file
3937
include/fmt/format.h
Normal file
File diff suppressed because it is too large
Load Diff
162
include/fmt/ostream.h
Normal file
162
include/fmt/ostream.h
Normal file
@ -0,0 +1,162 @@
|
||||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ostream>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <class Char>
|
||||
class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
typedef typename std::basic_streambuf<Char>::int_type int_type;
|
||||
typedef typename std::basic_streambuf<Char>::traits_type traits_type;
|
||||
|
||||
basic_buffer<Char> &buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(basic_buffer<Char> &buffer) : buffer_(buffer) {}
|
||||
|
||||
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:
|
||||
struct null;
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void operator<<(null);
|
||||
};
|
||||
|
||||
// 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 decltype(
|
||||
internal::declval<test_stream<Char>&>()
|
||||
<< internal::declval<U>(), std::true_type()) test(int);
|
||||
|
||||
template <typename>
|
||||
static std::false_type test(...);
|
||||
|
||||
typedef decltype(test<T>(0)) result;
|
||||
|
||||
public:
|
||||
// std::string operator<< is not considered user-defined because we handle strings
|
||||
// specially.
|
||||
static const bool value = result::value && !std::is_same<T, std::string>::value;
|
||||
};
|
||||
|
||||
// Disable conversion to int if T has an overloaded operator<< which is a free
|
||||
// function (not a member of std::ostream).
|
||||
template <typename T, typename Char>
|
||||
class convert_to_int<T, Char, true> {
|
||||
public:
|
||||
static const bool value =
|
||||
convert_to_int<T, Char, false>::value && !is_streamable<T, Char>::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) {
|
||||
const Char *data = buf.data();
|
||||
typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize;
|
||||
UnsignedStreamSize size = buf.size();
|
||||
UnsignedStreamSize max_size =
|
||||
internal::to_unsigned((std::numeric_limits<std::streamsize>::max)());
|
||||
do {
|
||||
UnsignedStreamSize n = size <= max_size ? size : max_size;
|
||||
os.write(data, static_cast<std::streamsize>(n));
|
||||
data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(basic_buffer<Char> &buffer, const T &value) {
|
||||
internal::formatbuf<Char> format_buf(buffer);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
output << value;
|
||||
buffer.resize(buffer.size());
|
||||
}
|
||||
|
||||
// Disable builtin formatting of enums and use operator<< instead.
|
||||
template <typename T>
|
||||
struct format_enum<T,
|
||||
typename std::enable_if<std::is_enum<T>::value>::type> : std::false_type {};
|
||||
} // namespace internal
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct formatter<T, Char,
|
||||
typename std::enable_if<internal::is_streamable<T, Char>::value>::type>
|
||||
: formatter<basic_string_view<Char>, Char> {
|
||||
|
||||
template <typename Context>
|
||||
auto format(const T &value, Context &ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::format_value(buffer, value);
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline void vprint(std::basic_ostream<Char> &os,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<typename buffer_context<Char>::type> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
}
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(std::ostream &os, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint<char>(os, format_str, make_format_args<format_context>(args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(std::wostream &os, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint<wchar_t>(os, format_str, make_format_args<wformat_context>(args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
417
include/fmt/posix.h
Normal file
417
include/fmt/posix.h
Normal file
@ -0,0 +1,417 @@
|
||||
// A C++ interface to POSIX functions.
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_POSIX_H_
|
||||
#define FMT_POSIX_H_
|
||||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // for O_RDONLY
|
||||
#include <locale.h> // for locale_t
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // for strtod_l
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#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
|
||||
|
||||
// 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 typedefs 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_; }
|
||||
};
|
||||
|
||||
typedef basic_cstring_view<char> cstring_view;
|
||||
typedef basic_cstring_view<wchar_t> wcstring_view;
|
||||
|
||||
// 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_; }
|
||||
};
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE *file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE *f) : file_(f) {}
|
||||
|
||||
public:
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_DTOR_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.
|
||||
buffered_file(Proxy p) FMT_NOEXCEPT : file_(p.file) {}
|
||||
|
||||
// A "move constructor" for moving from an lvalue.
|
||||
buffered_file(buffered_file &f) FMT_NOEXCEPT : file_(f.file_) {
|
||||
f.file_ = FMT_NULL;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from a temporary.
|
||||
buffered_file &operator=(Proxy p) {
|
||||
close();
|
||||
file_ = p.file;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from an lvalue.
|
||||
buffered_file &operator=(buffered_file &other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = FMT_NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns a proxy object for moving from a temporary:
|
||||
// buffered_file file = buffered_file(...);
|
||||
operator Proxy() FMT_NOEXCEPT {
|
||||
Proxy p = {file_};
|
||||
file_ = FMT_NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
#else
|
||||
private:
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(buffered_file);
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = FMT_NULL;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file &&other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = FMT_NULL;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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...));
|
||||
}
|
||||
};
|
||||
|
||||
// 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.
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
#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 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 f = 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.
|
||||
FMT_API ~file() FMT_DTOR_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 std::size_t read(void *buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API 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.
|
||||
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();
|
||||
|
||||
#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \
|
||||
!defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__)
|
||||
# define FMT_LOCALE
|
||||
#endif
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class Locale {
|
||||
private:
|
||||
# ifdef _MSC_VER
|
||||
typedef _locale_t locale_t;
|
||||
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
|
||||
static locale_t newlocale(int category_mask, const char *locale, locale_t) {
|
||||
return _create_locale(category_mask, locale);
|
||||
}
|
||||
|
||||
static void freelocale(locale_t locale) {
|
||||
_free_locale(locale);
|
||||
}
|
||||
|
||||
static double strtod_l(const char *nptr, char **endptr, _locale_t locale) {
|
||||
return _strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(Locale);
|
||||
|
||||
public:
|
||||
typedef locale_t Type;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) {
|
||||
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 = FMT_NULL;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#if !FMT_USE_RVALUE_REFERENCES
|
||||
namespace std {
|
||||
// For compatibility with C++98.
|
||||
inline fmt::buffered_file &move(fmt::buffered_file &f) { return f; }
|
||||
inline fmt::file &move(fmt::file &f) { return f; }
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // FMT_POSIX_H_
|
728
include/fmt/printf.h
Normal file
728
include/fmt/printf.h
Normal file
@ -0,0 +1,728 @@
|
||||
// Formatting library for C++
|
||||
//
|
||||
// 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::fill_n
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// 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 = std::numeric_limits<int>::max();
|
||||
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 <= std::numeric_limits<int>::max();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler: public function<int> {
|
||||
public:
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, int>::type
|
||||
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 static_cast<int>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, int>::type 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 function<bool> {
|
||||
public:
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, bool>::type
|
||||
operator()(T value) { return value == 0; }
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, bool>::type
|
||||
operator()(T) { return false; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <>
|
||||
struct make_unsigned_or_bool<bool> {
|
||||
typedef bool type;
|
||||
};
|
||||
|
||||
template <typename T, typename Context>
|
||||
class arg_converter: public function<void> {
|
||||
private:
|
||||
typedef typename Context::char_type Char;
|
||||
|
||||
basic_format_arg<Context> &arg_;
|
||||
typename Context::char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context> &arg, Char type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's')
|
||||
operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
typename std::enable_if<std::is_integral<U>::value>::type
|
||||
operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
typedef typename std::conditional<
|
||||
std::is_same<T, void>::value, U, T>::type TargetType;
|
||||
if (const_check(sizeof(TargetType) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<int>(static_cast<TargetType>(value)));
|
||||
} else {
|
||||
typedef typename make_unsigned_or_bool<TargetType>::type Unsigned;
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<Unsigned>(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_ = internal::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
typename std::enable_if<!std::is_integral<U>::value>::type operator()(U) {
|
||||
// No coversion 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(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context>
|
||||
class char_converter: public function<void> {
|
||||
private:
|
||||
basic_format_arg<Context> &arg_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(char_converter);
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context> &arg) : arg_(arg) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value>::type
|
||||
operator()(T value) {
|
||||
typedef typename Context::char_type Char;
|
||||
arg_ = internal::make_arg<Context>(static_cast<Char>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value>::type operator()(T) {
|
||||
// No coversion needed for non-integral types.
|
||||
}
|
||||
};
|
||||
|
||||
// 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: public function<unsigned> {
|
||||
private:
|
||||
typedef basic_format_specs<Char> format_specs;
|
||||
|
||||
format_specs &spec_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(printf_width_handler);
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs &spec) : spec_(spec) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, unsigned>::type
|
||||
operator()(T value) {
|
||||
typedef typename internal::int_traits<T>::main_type UnsignedType;
|
||||
UnsignedType width = static_cast<UnsignedType>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
spec_.align_ = ALIGN_LEFT;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = std::numeric_limits<int>::max();
|
||||
if (width > int_max)
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, unsigned>::type
|
||||
operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
template <typename Range>
|
||||
class printf_arg_formatter;
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Char,
|
||||
typename ArgFormatter =
|
||||
printf_arg_formatter<back_insert_range<internal::basic_buffer<Char>>>>
|
||||
class basic_printf_context;
|
||||
|
||||
/**
|
||||
\rst
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Range>
|
||||
class printf_arg_formatter:
|
||||
public internal::function<
|
||||
typename internal::arg_formatter_base<Range>::iterator>,
|
||||
public internal::arg_formatter_base<Range> {
|
||||
private:
|
||||
typedef typename Range::value_type char_type;
|
||||
typedef decltype(internal::declval<Range>().begin()) iterator;
|
||||
typedef internal::arg_formatter_base<Range> base;
|
||||
typedef basic_printf_context<iterator, char_type> context_type;
|
||||
|
||||
context_type &context_;
|
||||
|
||||
void write_null_pointer(char) {
|
||||
this->spec().type_ = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
void write_null_pointer(wchar_t) {
|
||||
this->spec().type_ = 0;
|
||||
this->write(L"(nil)");
|
||||
}
|
||||
|
||||
public:
|
||||
typedef typename base::format_specs format_specs;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*buffer* is a reference to the output buffer and *spec* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(internal::basic_buffer<char_type> &buffer,
|
||||
format_specs &spec, context_type &ctx)
|
||||
: base(back_insert_range<internal::basic_buffer<char_type>>(buffer), spec),
|
||||
context_(ctx) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, iterator>::type
|
||||
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_spec = this->spec();
|
||||
if (fmt_spec.type_ != 's')
|
||||
return base::operator()(value ? 1 : 0);
|
||||
fmt_spec.type_ = 0;
|
||||
this->write(value != 0);
|
||||
} else if (std::is_same<T, char_type>::value) {
|
||||
format_specs &fmt_spec = this->spec();
|
||||
if (fmt_spec.type_ && fmt_spec.type_ != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_spec.flags_ = 0;
|
||||
fmt_spec.align_ = ALIGN_RIGHT;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
}
|
||||
return this->out();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value, iterator>::type
|
||||
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->spec().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->spec().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->spec().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_);
|
||||
return this->out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct printf_formatter {
|
||||
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()) {
|
||||
internal::format_value(internal::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/** This template formats data and writes the output to a writer. */
|
||||
template <typename OutputIt, typename Char, typename ArgFormatter>
|
||||
class basic_printf_context :
|
||||
private internal::context_base<
|
||||
OutputIt, basic_printf_context<OutputIt, Char, ArgFormatter>, Char> {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
typedef Char char_type;
|
||||
|
||||
template <typename T>
|
||||
struct formatter_type { typedef printf_formatter<T> type; };
|
||||
|
||||
private:
|
||||
typedef internal::context_base<OutputIt, basic_printf_context, Char> base;
|
||||
typedef typename base::format_arg format_arg;
|
||||
typedef basic_format_specs<char_type> format_specs;
|
||||
typedef internal::null_terminating_iterator<char_type> iterator;
|
||||
|
||||
void parse_flags(format_specs &spec, iterator &it);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is equal
|
||||
// to the maximum unsigned value, the next argument.
|
||||
format_arg get_arg(
|
||||
iterator it,
|
||||
unsigned arg_index = (std::numeric_limits<unsigned>::max)());
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
unsigned parse_header(iterator &it, format_specs &spec);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments and
|
||||
the writer 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)
|
||||
: base(out, format_str, args) {}
|
||||
|
||||
using base::parse_context;
|
||||
using base::out;
|
||||
using base::advance_to;
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
void format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
void basic_printf_context<OutputIt, Char, AF>::parse_flags(
|
||||
format_specs &spec, iterator &it) {
|
||||
for (;;) {
|
||||
switch (*it++) {
|
||||
case '-':
|
||||
spec.align_ = ALIGN_LEFT;
|
||||
break;
|
||||
case '+':
|
||||
spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
|
||||
break;
|
||||
case '0':
|
||||
spec.fill_ = '0';
|
||||
break;
|
||||
case ' ':
|
||||
spec.flags_ |= SIGN_FLAG;
|
||||
break;
|
||||
case '#':
|
||||
spec.flags_ |= HASH_FLAG;
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
typename basic_printf_context<OutputIt, Char, AF>::format_arg
|
||||
basic_printf_context<OutputIt, Char, AF>::get_arg(
|
||||
iterator it, unsigned arg_index) {
|
||||
(void)it;
|
||||
if (arg_index == std::numeric_limits<unsigned>::max())
|
||||
return this->do_get_arg(this->parse_context().next_arg_id());
|
||||
return base::get_arg(arg_index - 1);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
unsigned basic_printf_context<OutputIt, Char, AF>::parse_header(
|
||||
iterator &it, format_specs &spec) {
|
||||
unsigned arg_index = std::numeric_limits<unsigned>::max();
|
||||
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).
|
||||
internal::error_handler eh;
|
||||
unsigned value = parse_nonnegative_int(it, eh);
|
||||
if (*it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0')
|
||||
spec.fill_ = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
spec.width_ = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(spec, it);
|
||||
// Parse width.
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
spec.width_ = parse_nonnegative_int(it, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
spec.width_ =
|
||||
visit(internal::printf_width_handler<char_type>(spec), get_arg(it));
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
void basic_printf_context<OutputIt, Char, AF>::format() {
|
||||
auto &buffer = internal::get_container(this->out());
|
||||
auto start = iterator(this->parse_context());
|
||||
auto it = start;
|
||||
using internal::pointer_from;
|
||||
while (*it) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (*it == c) {
|
||||
buffer.append(pointer_from(start), pointer_from(it));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
buffer.append(pointer_from(start), pointer_from(it) - 1);
|
||||
|
||||
format_specs spec;
|
||||
spec.align_ = ALIGN_RIGHT;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
unsigned arg_index = parse_header(it, spec);
|
||||
|
||||
// Parse precision.
|
||||
if (*it == '.') {
|
||||
++it;
|
||||
if ('0' <= *it && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
spec.precision_ = static_cast<int>(parse_nonnegative_int(it, eh));
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
spec.precision_ =
|
||||
visit(internal::printf_precision_handler(), get_arg(it));
|
||||
} else {
|
||||
spec.precision_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(it, arg_index);
|
||||
if (spec.flag(HASH_FLAG) && visit(internal::is_zero_int(), arg))
|
||||
spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG);
|
||||
if (spec.fill_ == '0') {
|
||||
if (arg.is_arithmetic())
|
||||
spec.align_ = ALIGN_NUMERIC;
|
||||
else
|
||||
spec.fill_ = ' '; // Ignore '0' flag for non-numeric types.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
using internal::convert_arg;
|
||||
switch (*it++) {
|
||||
case 'h':
|
||||
if (*it == 'h')
|
||||
convert_arg<signed char>(arg, *++it);
|
||||
else
|
||||
convert_arg<short>(arg, *it);
|
||||
break;
|
||||
case 'l':
|
||||
if (*it == 'l')
|
||||
convert_arg<long long>(arg, *++it);
|
||||
else
|
||||
convert_arg<long>(arg, *it);
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, *it);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<std::size_t>(arg, *it);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, *it);
|
||||
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, *it);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (!*it)
|
||||
FMT_THROW(format_error("invalid format string"));
|
||||
spec.type_ = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (spec.type_) {
|
||||
case 'i': case 'u':
|
||||
spec.type_ = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
// TODO: handle wchar_t better?
|
||||
visit(internal::char_converter<basic_printf_context>(arg), arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
visit(AF(buffer, spec, *this), arg);
|
||||
}
|
||||
buffer.append(pointer_from(start), pointer_from(it));
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void printf(internal::basic_buffer<Char> &buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
|
||||
template <typename Buffer>
|
||||
struct printf_context {
|
||||
typedef basic_printf_context<
|
||||
std::back_insert_iterator<Buffer>, typename Buffer::value_type> type;
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
inline format_arg_store<printf_context<internal::buffer>::type, Args...>
|
||||
make_printf_args(const Args & ... args) {
|
||||
return format_arg_store<printf_context<internal::buffer>::type, Args...>(
|
||||
args...);
|
||||
}
|
||||
typedef basic_format_args<printf_context<internal::buffer>::type> printf_args;
|
||||
typedef basic_format_args<printf_context<internal::wbuffer>::type> wprintf_args;
|
||||
|
||||
inline std::string vsprintf(string_view format, printf_args args) {
|
||||
memory_buffer buffer;
|
||||
printf(buffer, 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... Args>
|
||||
inline std::string sprintf(string_view format_str, const Args & ... args) {
|
||||
return vsprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::buffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline std::wstring vsprintf(wstring_view format, wprintf_args args) {
|
||||
wmemory_buffer buffer;
|
||||
printf(buffer, format, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline std::wstring sprintf(wstring_view format_str, const Args & ... args) {
|
||||
return vsprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline int vfprintf(std::FILE *f, basic_string_view<Char> format,
|
||||
basic_format_args<typename printf_context<
|
||||
internal::basic_buffer<Char>>::type> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, format, args);
|
||||
std::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... Args>
|
||||
inline int fprintf(std::FILE *f, string_view format_str, const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf<char>(f, format_str, vargs);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::FILE *f, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
return vfprintf(f, format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline int vprintf(string_view format, printf_args args) {
|
||||
return vfprintf(stdout, format, args);
|
||||
}
|
||||
|
||||
inline int vprintf(wstring_view format, wprintf_args args) {
|
||||
return vfprintf(stdout, format, args);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int printf(string_view format_str, const Args & ... args) {
|
||||
return vprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::buffer>::type>(args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int printf(wstring_view format_str, const Args & ... args) {
|
||||
return vprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline int vfprintf(std::ostream &os, string_view format_str,
|
||||
printf_args args) {
|
||||
memory_buffer buffer;
|
||||
printf(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
inline int vfprintf(std::wostream &os, wstring_view format_str,
|
||||
wprintf_args args) {
|
||||
wmemory_buffer buffer;
|
||||
printf(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::ostream &os, string_view format_str,
|
||||
const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf(os, format_str, vargs);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::wostream &os, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf(os, format_str, vargs);
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
305
include/fmt/ranges.h
Normal file
305
include/fmt/ranges.h
Normal file
@ -0,0 +1,305 @@
|
||||
// Formatting library for C++ - the core API
|
||||
//
|
||||
// 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 "format.h"
|
||||
#include <type_traits>
|
||||
|
||||
// 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 std::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 internal {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
void copy(const RangeT &range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
void copy(const char *str, OutputIterator out) {
|
||||
const char *p_curr = str;
|
||||
while (*p_curr) {
|
||||
*out++ = *p_curr++;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
void copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
}
|
||||
|
||||
/// 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(p->find('a'), p->length(), p->data(), int());
|
||||
template <typename>
|
||||
static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
|
||||
};
|
||||
|
||||
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, typename std::conditional<
|
||||
false,
|
||||
conditional_helper<decltype(internal::declval<T>().begin()),
|
||||
decltype(internal::declval<T>().end())>,
|
||||
void>::type> : 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,
|
||||
internal::declval<typename std::tuple_element<0, U>::type>(), int());
|
||||
template <typename>
|
||||
static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(FMT_NULL))>::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 <std::size_t... N>
|
||||
using index_sequence = std::index_sequence<N...>;
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N>
|
||||
struct integer_sequence {
|
||||
typedef T value_type;
|
||||
|
||||
static FMT_CONSTEXPR std::size_t size() {
|
||||
return sizeof...(N);
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t... N>
|
||||
using index_sequence = integer_sequence<std::size_t, N...>;
|
||||
|
||||
template <typename T, std::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 <std::size_t N>
|
||||
using make_index_sequence = make_integer_sequence<std::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_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
|
||||
typename std::enable_if<
|
||||
!is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
|
||||
return add_space ? " {}" : "{}";
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
|
||||
typename std::enable_if<
|
||||
is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
|
||||
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 internal
|
||||
|
||||
template <typename T>
|
||||
struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char,
|
||||
typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> {
|
||||
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++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
std::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();
|
||||
std::size_t i = 0;
|
||||
internal::copy(formatting.prefix, out);
|
||||
|
||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value && !internal::is_like_std_string<T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
typename std::enable_if<fmt::is_range<RangeT>::value>::type> {
|
||||
|
||||
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 = ctx.out();
|
||||
internal::copy(formatting.prefix, out);
|
||||
std::size_t i = 0;
|
||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
format_to(out, " ... <other elements>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
152
include/fmt/time.h
Normal file
152
include/fmt/time.h
Normal file
@ -0,0 +1,152 @@
|
||||
// Formatting library for C++ - time formatting
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_TIME_H_
|
||||
#define FMT_TIME_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ctime>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace internal{
|
||||
inline null<> localtime_r(...) { return null<>(); }
|
||||
inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::localtime
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
std::tm *tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
dispatcher lt(time);
|
||||
if (lt.run())
|
||||
return lt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::gmtime
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::null<>) {
|
||||
std::tm *tm = std::gmtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
dispatcher gt(time);
|
||||
if (gt.run())
|
||||
return gt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
inline std::size_t strftime(char *str, std::size_t count, const char *format,
|
||||
const std::tm *time) {
|
||||
return std::strftime(str, count, format, time);
|
||||
}
|
||||
|
||||
inline std::size_t strftime(wchar_t *str, std::size_t count,
|
||||
const wchar_t *format, const std::tm *time) {
|
||||
return std::wcsftime(str, count, format, time);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::tm, Char> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
auto it = internal::null_terminating_iterator<Char>(ctx);
|
||||
if (*it == ':')
|
||||
++it;
|
||||
auto end = it;
|
||||
while (*end && *end != '}')
|
||||
++end;
|
||||
tm_format.reserve(end - it + 1);
|
||||
using internal::pointer_from;
|
||||
tm_format.append(pointer_from(it), pointer_from(end));
|
||||
tm_format.push_back('\0');
|
||||
return pointer_from(end);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
internal::basic_buffer<Char> &buf = internal::get_container(ctx.out());
|
||||
std::size_t start = buf.size();
|
||||
for (;;) {
|
||||
std::size_t size = buf.capacity() - start;
|
||||
std::size_t count =
|
||||
internal::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||
if (count != 0) {
|
||||
buf.resize(start + count);
|
||||
break;
|
||||
}
|
||||
if (size >= tm_format.size() * 256) {
|
||||
// If the buffer is 256 times larger than the format string, assume
|
||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
||||
// better way to distinguish the two cases:
|
||||
// https://github.com/fmtlib/fmt/issues/367
|
||||
break;
|
||||
}
|
||||
const std::size_t MIN_GROWTH = 10;
|
||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
basic_memory_buffer<Char> tm_format;
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_TIME_H_
|
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_
|
52
src/format.cc
Normal file
52
src/format.cc
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 internal {
|
||||
// Force linking of inline functions into the library.
|
||||
std::string (*vformat_ref)(string_view, format_args) = vformat;
|
||||
std::wstring (*vformat_wref)(wstring_view, wformat_args) = vformat;
|
||||
}
|
||||
|
||||
template struct internal::basic_data<void>;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API char internal::thousands_sep(locale_provider *lp);
|
||||
|
||||
template void basic_fixed_buffer<char>::grow(std::size_t);
|
||||
|
||||
template void internal::arg_map<format_context>::init(
|
||||
const basic_format_args<format_context> &args);
|
||||
|
||||
template FMT_API int internal::char_traits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format, int precision,
|
||||
double value);
|
||||
|
||||
template FMT_API int internal::char_traits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format, int precision,
|
||||
long double value);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API wchar_t internal::thousands_sep(locale_provider *lp);
|
||||
|
||||
template void basic_fixed_buffer<wchar_t>::grow(std::size_t);
|
||||
|
||||
template void internal::arg_map<wformat_context>::init(
|
||||
const basic_format_args<wformat_context> &args);
|
||||
|
||||
template FMT_API int internal::char_traits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
||||
int precision, double value);
|
||||
|
||||
template FMT_API int internal::char_traits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
||||
int precision, long double value);
|
||||
FMT_END_NAMESPACE
|
@ -1,36 +1,16 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
// A C++ interface to POSIX functions.
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "posix.h"
|
||||
#include "fmt/posix.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
@ -39,6 +19,9 @@
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
|
||||
@ -55,11 +38,14 @@
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# undef fileno
|
||||
# endif
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
@ -78,38 +64,40 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
}
|
||||
|
||||
fmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT {
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
buffered_file::~buffered_file() FMT_NOEXCEPT {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
fmt::report_system_error(errno, "cannot close file");
|
||||
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);
|
||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_,
|
||||
FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), FMT_NULL);
|
||||
if (!file_)
|
||||
throw SystemError(errno, "cannot open file {}", filename);
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
}
|
||||
|
||||
void fmt::BufferedFile::close() {
|
||||
void buffered_file::close() {
|
||||
if (!file_)
|
||||
return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = 0;
|
||||
file_ = FMT_NULL;
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot close file");
|
||||
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 fmt::BufferedFile::fileno() const {
|
||||
int buffered_file::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1)
|
||||
throw SystemError(errno, "cannot get file descriptor");
|
||||
FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
return fd;
|
||||
}
|
||||
|
||||
fmt::File::File(fmt::CStringRef path, int oflag) {
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
@ -118,17 +106,17 @@ fmt::File::File(fmt::CStringRef path, int oflag) {
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
#endif
|
||||
if (fd_ == -1)
|
||||
throw SystemError(errno, "cannot open file {}", path);
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
}
|
||||
|
||||
fmt::File::~File() FMT_NOEXCEPT {
|
||||
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");
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
void fmt::File::close() {
|
||||
void file::close() {
|
||||
if (fd_ == -1)
|
||||
return;
|
||||
// Don't retry close in case of EINTR!
|
||||
@ -136,10 +124,10 @@ void fmt::File::close() {
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot close file");
|
||||
FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
fmt::LongLong fmt::File::size() const {
|
||||
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.
|
||||
@ -150,63 +138,63 @@ fmt::LongLong fmt::File::size() const {
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
throw WindowsError(GetLastError(), "cannot get file size");
|
||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
||||
}
|
||||
fmt::ULongLong long_size = size_upper;
|
||||
unsigned long long 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");
|
||||
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 fmt::File::read(void *buffer, std::size_t count) {
|
||||
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)
|
||||
throw SystemError(errno, "cannot read from file");
|
||||
return result;
|
||||
FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t fmt::File::write(const void *buffer, std::size_t count) {
|
||||
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)
|
||||
throw SystemError(errno, "cannot write to file");
|
||||
return result;
|
||||
FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
fmt::File fmt::File::dup(int fd) {
|
||||
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)
|
||||
throw SystemError(errno, "cannot duplicate file descriptor {}", fd);
|
||||
return File(new_fd);
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
void fmt::File::dup2(int fd) {
|
||||
void 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);
|
||||
FMT_THROW(system_error(errno,
|
||||
"cannot duplicate file descriptor {} to {}", fd_, fd));
|
||||
}
|
||||
}
|
||||
|
||||
void fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT {
|
||||
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 = ErrorCode(errno);
|
||||
ec = error_code(errno);
|
||||
}
|
||||
|
||||
void fmt::File::pipe(File &read_end, File &write_end) {
|
||||
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();
|
||||
@ -222,24 +210,25 @@ void fmt::File::pipe(File &read_end, File &write_end) {
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
#endif
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot create pipe");
|
||||
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]);
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
fmt::BufferedFile fmt::File::fdopen(const char *mode) {
|
||||
buffered_file 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);
|
||||
FMT_THROW(system_error(errno,
|
||||
"cannot associate stream with file descriptor"));
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return file;
|
||||
return bf;
|
||||
}
|
||||
|
||||
long fmt::getpagesize() {
|
||||
long getpagesize() {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
@ -247,7 +236,9 @@ long fmt::getpagesize() {
|
||||
#else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0)
|
||||
throw SystemError(errno, "cannot get memory page size");
|
||||
FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
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="fmt" />
|
@ -2,3 +2,5 @@ This directory contains build support files such as
|
||||
|
||||
* CMake modules
|
||||
* Build scripts
|
||||
* qmake (static build with dynamic libc only)
|
||||
|
||||
|
42
support/appveyor-build.py
Executable file → Normal file
42
support/appveyor-build.py
Executable file → Normal file
@ -6,26 +6,34 @@ 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 2013':
|
||||
generator = 'Visual Studio 12 2013'
|
||||
elif 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,33 @@ configuration:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
platform:
|
||||
- Win32
|
||||
- x64
|
||||
|
||||
image:
|
||||
- Visual Studio 2013
|
||||
- Visual Studio 2015
|
||||
- Visual Studio 2017
|
||||
|
||||
environment:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
matrix:
|
||||
- BUILD: msvc
|
||||
- BUILD: msvc
|
||||
PLATFORM: x64
|
||||
- BUILD: mingw
|
||||
MSVC_DEFAULT_OPTIONS: ON
|
||||
BUILD: msvc
|
||||
|
||||
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
|
||||
}
|
98
support/build.gradle
Normal file
98
support/build.gradle
Normal file
@ -0,0 +1,98 @@
|
||||
// General gradle arguments for root project
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// Be general, as much as possible ...
|
||||
// universalApk true
|
||||
|
||||
// Specify platforms for Application
|
||||
reset()
|
||||
include "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21 // Android 5.0+
|
||||
targetSdkVersion 25 // Follow Compile SDK
|
||||
versionCode 16 // Follow release count
|
||||
versionName "4.1.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++14"
|
||||
}
|
||||
}
|
||||
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'
|
||||
}
|
@ -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 ()
|
97
support/cmake/cxx14.cmake
Normal file
97
support/cmake/cxx14.cmake
Normal file
@ -0,0 +1,97 @@
|
||||
# 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 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() {}" SUPPORTS_VARIADIC_TEMPLATES)
|
||||
if (NOT SUPPORTS_VARIADIC_TEMPLATES)
|
||||
set (SUPPORTS_VARIADIC_TEMPLATES OFF)
|
||||
endif ()
|
||||
|
||||
# Check if initializer lists are supported.
|
||||
check_cxx_source_compiles("
|
||||
#include <initializer_list>
|
||||
int main() {}" SUPPORTS_INITIALIZER_LIST)
|
||||
if (NOT SUPPORTS_INITIALIZER_LIST)
|
||||
set (SUPPORTS_INITIALIZER_LIST OFF)
|
||||
endif ()
|
||||
|
||||
# Check if enum bases are available
|
||||
check_cxx_source_compiles("
|
||||
enum C : char {A};
|
||||
int main() {}"
|
||||
SUPPORTS_ENUM_BASE)
|
||||
if (NOT SUPPORTS_ENUM_BASE)
|
||||
set (SUPPORTS_ENUM_BASE OFF)
|
||||
endif ()
|
||||
|
||||
# Check if type traits are available
|
||||
check_cxx_source_compiles("
|
||||
#include <type_traits>
|
||||
class C { void operator=(const C&); };
|
||||
int main() { static_assert(!std::is_copy_assignable<C>::value, \"\"); }"
|
||||
SUPPORTS_TYPE_TRAITS)
|
||||
if (NOT SUPPORTS_TYPE_TRAITS)
|
||||
set (SUPPORTS_TYPE_TRAITS OFF)
|
||||
endif ()
|
||||
|
||||
# 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 ()
|
||||
|
||||
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)
|
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 % 4 == 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
|
260
support/manage.py
Executable file
260
support/manage.py
Executable file
@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Manage site and releases.
|
||||
|
||||
Usage:
|
||||
manage.py release [<branch>]
|
||||
manage.py site
|
||||
"""
|
||||
|
||||
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']:
|
||||
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"" _')
|
||||
# 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),
|
||||
params=params, files={package: 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)
|
127
support/rst2md.py
Normal file
127
support/rst2md.py
Normal file
@ -0,0 +1,127 @@
|
||||
# reStructuredText (RST) to GitHub-flavored Markdown converter
|
||||
|
||||
import re
|
||||
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 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
|
||||
|
||||
|
||||
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())
|
@ -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 <http://fmtlib.net/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=http://fmtlib.net/latest/">
|
||||
<script type="text/javascript">
|
||||
window.location.href = "http://cppformat.github.io/latest/"
|
||||
window.location.href = "http://fmtlib.net/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='http://fmtlib.net/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,112 @@
|
||||
# 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 library.
|
||||
makedirs_if_not_exist(build_dir)
|
||||
cmake_flags = [
|
||||
'-DCMAKE_INSTALL_PREFIX=' + install_dir, '-DCMAKE_BUILD_TYPE=' + build,
|
||||
'-DCMAKE_CXX_STANDARD=' + standard
|
||||
]
|
||||
check_call(['cmake', '-DFMT_DOC=OFF', '-DFMT_PEDANTIC=ON', '-DFMT_WERROR=ON', fmt_dir] +
|
||||
cmake_flags, cwd=build_dir)
|
||||
|
||||
# Build library.
|
||||
check_call(['make', '-j4'], cwd=build_dir)
|
||||
|
||||
# Test 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 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,20 +17,8 @@ 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)
|
||||
|
||||
# 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)
|
||||
if (NOT SUPPORTS_VARIADIC_TEMPLATES OR NOT SUPPORTS_INITIALIZER_LIST)
|
||||
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
|
||||
endif ()
|
||||
|
||||
# Workaround a bug in implementation of variadic templates in MSVC11.
|
||||
@ -43,113 +31,164 @@ 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=0)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# 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_compile_definitions(test-main PUBLIC
|
||||
FMT_USE_FILE_DESCRIPTORS=$<BOOL:${HAVE_OPEN}>)
|
||||
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.
|
||||
target_compile_definitions(${name} PRIVATE
|
||||
FMT_USE_TYPE_TRAITS=$<BOOL:${SUPPORTS_TYPE_TRAITS}>
|
||||
FMT_USE_ENUM_BASE=$<BOOL:${SUPPORTS_ENUM_BASE}>)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
|
||||
add_test(NAME ${name} COMMAND ${name})
|
||||
endfunction()
|
||||
|
||||
add_fmt_test(assert-test)
|
||||
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)
|
||||
endif ()
|
||||
|
||||
add_fmt_test(format-impl-test CUSTOM_LINK)
|
||||
add_fmt_test(format-impl-test)
|
||||
add_fmt_test(ostream-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(time-test)
|
||||
add_fmt_test(util-test mock-allocator.h)
|
||||
if (CPP11_FLAG)
|
||||
set_target_properties(util-test PROPERTIES COMPILE_FLAGS ${CPP11_FLAG})
|
||||
endif ()
|
||||
|
||||
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)
|
||||
add_fmt_test(custom-formatter-test)
|
||||
add_fmt_test(ranges-test)
|
||||
|
||||
if (HAVE_OPEN)
|
||||
add_executable(posix-mock-test posix-mock-test.cc ../format.cc ${TEST_MAIN_SRC})
|
||||
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_compile_definitions(posix-mock-test PRIVATE FMT_USE_FILE_DESCRIPTORS=1)
|
||||
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 ()
|
||||
add_test(NAME posix-mock-test COMMAND posix-mock-test)
|
||||
add_fmt_test(posix-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)
|
||||
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 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)
|
||||
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 ()
|
||||
target_include_directories(noexception-test SYSTEM PUBLIC gtest gmock)
|
||||
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)
|
||||
# Test that the library compiles without windows.h.
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
add_library(no-windows-h-test ../src/format.cc)
|
||||
target_include_directories(
|
||||
no-windows-h-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(no-windows-h-test PRIVATE FMT_USE_WINDOWS_H=0)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(no-windows-h-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
target_include_directories(no-windows-h-test SYSTEM PUBLIC gtest gmock)
|
||||
endif ()
|
||||
|
||||
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})
|
||||
--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}")
|
||||
|
||||
# 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 if the targets are findable 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 findable 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 ()
|
||||
|
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,32 +1,12 @@
|
||||
/*
|
||||
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"
|
||||
#include "fmt/core.h"
|
||||
#include "gtest.h"
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
@ -37,5 +17,6 @@
|
||||
#endif
|
||||
|
||||
TEST(AssertTest, Fail) {
|
||||
EXPECT_DEBUG_DEATH_IF_SUPPORTED(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||
EXPECT_DEBUG_DEATH_IF_SUPPORTED(
|
||||
FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||
}
|
||||
|
@ -1,41 +1,79 @@
|
||||
# Test if compile errors are produced where necessary.
|
||||
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
function (expect_compile_error code)
|
||||
check_cxx_source_compiles("
|
||||
#include \"format.cc\"
|
||||
#include \"posix.h\"
|
||||
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() {
|
||||
${code}
|
||||
${fragment}
|
||||
}
|
||||
" compiles)
|
||||
set (does_compile ${compiles})
|
||||
" 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 (does_compile)
|
||||
message(FATAL_ERROR "No compile error for: ${code}")
|
||||
if (error_msg)
|
||||
message(FATAL_ERROR ${error_msg})
|
||||
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);")
|
||||
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 ()
|
||||
|
||||
# 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' ');")
|
||||
# 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';")
|
||||
expect_compile_error("fmt::format(\"{}\", L'a');")
|
||||
|
||||
expect_compile_error("FMT_STATIC_ASSERT(0 > 1, \"oops\");")
|
||||
# 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")
|
||||
|
51
test/custom-formatter-test.cc
Normal file
51
test/custom-formatter-test.cc
Normal file
@ -0,0 +1,51 @@
|
||||
// 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.
|
||||
|
||||
#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::arg_formatter<fmt::back_insert_range<fmt::internal::buffer>> {
|
||||
public:
|
||||
typedef fmt::back_insert_range<fmt::internal::buffer> range;
|
||||
typedef fmt::arg_formatter<range> base;
|
||||
|
||||
custom_arg_formatter(fmt::format_context &ctx, fmt::format_specs &s)
|
||||
: base(ctx, s) {}
|
||||
|
||||
using base::operator();
|
||||
|
||||
iterator operator()(double value) {
|
||||
// Comparing a float to 0.0 is safe
|
||||
if (round(value * pow(10, spec().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;
|
||||
// Pass custom argument formatter as a template arg to vwrite.
|
||||
fmt::vformat_to<custom_arg_formatter>(buffer, 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]);
|
||||
}
|
@ -1,65 +1,63 @@
|
||||
/*
|
||||
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_EXTENDED_COLORS
|
||||
#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 "../src/format.cc"
|
||||
#include "fmt/printf.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
template <typename T>
|
||||
struct ValueExtractor: fmt::internal::function<T> {
|
||||
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()));
|
||||
}
|
||||
};
|
||||
|
||||
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 = std::numeric_limits<long long>::max();
|
||||
auto arg = fmt::internal::make_arg<fmt::format_context>(value);
|
||||
visit(fmt::internal::arg_converter<long long, fmt::format_context>(arg, 'd'),
|
||||
arg);
|
||||
EXPECT_EQ(value, visit(ValueExtractor<long long>(), arg));
|
||||
}
|
||||
|
||||
TEST(FormatTest, FormatNegativeNaN) {
|
||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
if (fmt::internal::FPUtil::isnegative(-nan))
|
||||
if (fmt::internal::fputil::isnegative(-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::safe_strerror(EDOM, message = nullptr, 0), "invalid buffer");
|
||||
EXPECT_ASSERT(fmt::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
|
||||
@ -89,25 +87,40 @@ 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::format_error_code(buffer, 42, "test");
|
||||
EXPECT_EQ("test: " + msg, to_string(buffer));
|
||||
}
|
||||
{
|
||||
fmt::MemoryWriter w;
|
||||
fmt::memory_buffer buffer;
|
||||
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::inline_buffer_size - msg.size() - sep.size() + 1, 'x');
|
||||
fmt::format_error_code(buffer, 42, prefix);
|
||||
EXPECT_EQ(msg, to_string(buffer));
|
||||
}
|
||||
{
|
||||
fmt::MemoryWriter w;
|
||||
int codes[] = {42, -1};
|
||||
for (std::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::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());
|
||||
fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
|
||||
fmt::format_error_code(buffer, codes[i], prefix);
|
||||
EXPECT_EQ(prefix + sep + msg, to_string(buffer));
|
||||
std::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::format_error_code(buffer, codes[i], prefix);
|
||||
EXPECT_EQ(msg, to_string(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ColorsTest, Colors) {
|
||||
EXPECT_WRITE(stdout, fmt::print(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(fmt::color::blue,"blue"),
|
||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||
}
|
||||
|
1454
test/format-test.cc
1454
test/format-test.cc
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,7 @@
|
||||
|
||||
// This line ensures that gtest.h can be compiled on its own, even
|
||||
// when it's fused.
|
||||
#include "gtest/gtest.h"
|
||||
#include "gtest.h"
|
||||
|
||||
// The following lines pull in the real gtest *.cc files.
|
||||
// Copyright 2005, Google Inc.
|
@ -36,6 +36,10 @@
|
||||
#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_H_
|
||||
#define GMOCK_INCLUDE_GMOCK_GMOCK_H_
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang diagnostic ignored "-Wc99-extensions"
|
||||
#endif
|
||||
|
||||
// This file implements the following syntax:
|
||||
//
|
||||
// ON_CALL(mock_object.Method(...))
|
||||
@ -231,7 +235,7 @@
|
||||
|
||||
// Most of the types needed for porting Google Mock are also required
|
||||
// for Google Test and are defined in gtest-port.h.
|
||||
#include "gtest/gtest.h"
|
||||
#include "gtest.h"
|
||||
|
||||
// To avoid conditional compilation everywhere, we make it
|
||||
// gmock-port.h's responsibility to #include the header implementing
|
||||
@ -821,8 +825,9 @@ template <typename T> struct DecayArray<T[]> {
|
||||
// crashes).
|
||||
template <typename T>
|
||||
inline T Invalid() {
|
||||
void *p = NULL;
|
||||
return const_cast<typename remove_reference<T>::type&>(
|
||||
*static_cast<volatile typename remove_reference<T>::type*>(NULL));
|
||||
*static_cast<volatile typename remove_reference<T>::type*>(p));
|
||||
}
|
||||
template <>
|
||||
inline void Invalid<void>() {}
|
||||
@ -10086,8 +10091,9 @@ class FunctionMockerBase : public UntypedFunctionMockerBase {
|
||||
// threads concurrently.
|
||||
Result InvokeWith(const ArgumentTuple& args)
|
||||
GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
|
||||
return static_cast<const ResultHolder*>(
|
||||
this->UntypedInvokeWith(&args))->GetValueAndDelete();
|
||||
const ResultHolder *rh = static_cast<const ResultHolder*>(
|
||||
this->UntypedInvokeWith(&args));
|
||||
return rh ? rh->GetValueAndDelete() : Result();
|
||||
}
|
||||
|
||||
// Adds and returns a default action spec for this mock function.
|
@ -1,29 +1,9 @@
|
||||
/*
|
||||
Tests of custom Google Test assertions.
|
||||
|
||||
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++ - tests of custom Google Test assertions
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "gtest-extra.h"
|
||||
|
||||
@ -46,7 +26,7 @@ namespace {
|
||||
std::string sanitize(const std::string &s) {
|
||||
std::string result;
|
||||
for (std::string::const_iterator i = s.begin(), end = s.end(); i != end; ++i)
|
||||
result.push_back(*i & 0xff);
|
||||
result.push_back(static_cast<char>(*i & 0xff));
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -78,7 +58,7 @@ void throw_exception() {
|
||||
}
|
||||
|
||||
void throw_system_error() {
|
||||
throw fmt::SystemError(EDOM, "test");
|
||||
throw fmt::system_error(EDOM, "test");
|
||||
}
|
||||
|
||||
// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument
|
||||
@ -207,11 +187,11 @@ TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
// EXPECT_SYSTEM_ERROR macro.
|
||||
TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
int n = 0;
|
||||
EXPECT_SYSTEM_ERROR(throw fmt::SystemError(EDOM, "test"), EDOM, "test");
|
||||
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), "");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(
|
||||
throw fmt::SystemError(EDOM, "aaa"), EDOM, "bbb"), "");
|
||||
throw fmt::system_error(EDOM, "aaa"), EDOM, "bbb"), "");
|
||||
}
|
||||
|
||||
TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
|
||||
@ -268,10 +248,10 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"),
|
||||
"Expected: throw_exception() throws an exception of "
|
||||
"type fmt::SystemError.\n Actual: it throws a different type.");
|
||||
"type fmt::system_error.\n Actual: it throws a different type.");
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"),
|
||||
"Expected: do_nothing() throws an exception of type fmt::SystemError.\n"
|
||||
"Expected: do_nothing() throws an exception of type fmt::system_error.\n"
|
||||
" Actual: it throws nothing.");
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"),
|
||||
@ -319,27 +299,27 @@ TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||
}
|
||||
|
||||
TEST(UtilTest, FormatSystemError) {
|
||||
fmt::MemoryWriter out;
|
||||
fmt::internal::format_system_error(out, EDOM, "test message");
|
||||
EXPECT_EQ(out.str(), format_system_error(EDOM, "test message"));
|
||||
fmt::memory_buffer out;
|
||||
fmt::format_system_error(out, EDOM, "test message");
|
||||
EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message"));
|
||||
}
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
|
||||
using fmt::BufferedFile;
|
||||
using fmt::ErrorCode;
|
||||
using fmt::File;
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
using fmt::file;
|
||||
|
||||
TEST(ErrorCodeTest, Ctor) {
|
||||
EXPECT_EQ(0, ErrorCode().get());
|
||||
EXPECT_EQ(42, ErrorCode(42).get());
|
||||
EXPECT_EQ(0, error_code().get());
|
||||
EXPECT_EQ(42, error_code(42).get());
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, ScopedRedirect) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
{
|
||||
BufferedFile file(write_end.fdopen("w"));
|
||||
buffered_file file(write_end.fdopen("w"));
|
||||
std::fprintf(file.get(), "[[[");
|
||||
{
|
||||
OutputRedirect redir(file.get());
|
||||
@ -352,53 +332,53 @@ TEST(OutputRedirectTest, ScopedRedirect) {
|
||||
|
||||
// Test that OutputRedirect handles errors in flush correctly.
|
||||
TEST(OutputRedirectTest, FlushErrorInCtor) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int write_fd = write_end.descriptor();
|
||||
File write_copy = write_end.dup(write_fd);
|
||||
BufferedFile f = write_end.fdopen("w");
|
||||
file write_copy = write_end.dup(write_fd);
|
||||
buffered_file f = write_end.fdopen("w");
|
||||
// Put a character in a file buffer.
|
||||
EXPECT_EQ('x', fputc('x', f.get()));
|
||||
FMT_POSIX(close(write_fd));
|
||||
scoped_ptr<OutputRedirect> redir;
|
||||
scoped_ptr<OutputRedirect> redir{nullptr};
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new OutputRedirect(f.get())),
|
||||
EBADF, "cannot flush stream");
|
||||
redir.reset();
|
||||
redir.reset(nullptr);
|
||||
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, DupErrorInCtor) {
|
||||
BufferedFile f = open_buffered_file();
|
||||
buffered_file f = open_buffered_file();
|
||||
int fd = (f.fileno)();
|
||||
File copy = File::dup(fd);
|
||||
file copy = file::dup(fd);
|
||||
FMT_POSIX(close(fd));
|
||||
scoped_ptr<OutputRedirect> redir;
|
||||
scoped_ptr<OutputRedirect> redir{nullptr};
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new OutputRedirect(f.get())),
|
||||
EBADF, fmt::format("cannot duplicate file descriptor {}", fd));
|
||||
copy.dup2(fd); // "undo" close or dtor will fail
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, RestoreAndRead) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
BufferedFile file(write_end.fdopen("w"));
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
buffered_file file(write_end.fdopen("w"));
|
||||
std::fprintf(file.get(), "[[[");
|
||||
OutputRedirect redir(file.get());
|
||||
std::fprintf(file.get(), "censored");
|
||||
EXPECT_EQ("censored", sanitize(redir.restore_and_read()));
|
||||
EXPECT_EQ("", sanitize(redir.restore_and_read()));
|
||||
std::fprintf(file.get(), "]]]");
|
||||
file = BufferedFile();
|
||||
file = buffered_file();
|
||||
EXPECT_READ(read_end, "[[[]]]");
|
||||
}
|
||||
|
||||
// Test that OutputRedirect handles errors in flush correctly.
|
||||
TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int write_fd = write_end.descriptor();
|
||||
File write_copy = write_end.dup(write_fd);
|
||||
BufferedFile f = write_end.fdopen("w");
|
||||
file write_copy = write_end.dup(write_fd);
|
||||
buffered_file f = write_end.fdopen("w");
|
||||
OutputRedirect redir(f.get());
|
||||
// Put a character in a file buffer.
|
||||
EXPECT_EQ('x', fputc('x', f.get()));
|
||||
@ -409,11 +389,11 @@ TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
|
||||
}
|
||||
|
||||
TEST(OutputRedirectTest, ErrorInDtor) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int write_fd = write_end.descriptor();
|
||||
File write_copy = write_end.dup(write_fd);
|
||||
BufferedFile f = write_end.fdopen("w");
|
||||
file write_copy = write_end.dup(write_fd);
|
||||
buffered_file f = write_end.fdopen("w");
|
||||
scoped_ptr<OutputRedirect> redir(new OutputRedirect(f.get()));
|
||||
// Put a character in a file buffer.
|
||||
EXPECT_EQ('x', fputc('x', f.get()));
|
||||
@ -423,9 +403,9 @@ TEST(OutputRedirectTest, ErrorInDtor) {
|
||||
// output in EXPECT_STDERR and the second close will break output
|
||||
// redirection.
|
||||
FMT_POSIX(close(write_fd));
|
||||
SUPPRESS_ASSERT(redir.reset());
|
||||
SUPPRESS_ASSERT(redir.reset(nullptr));
|
||||
}, format_system_error(EBADF, "cannot flush stream"));
|
||||
write_copy.dup2(write_fd); // "undo" close or dtor of BufferedFile will fail
|
||||
write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail
|
||||
}
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
|
@ -1,35 +1,15 @@
|
||||
/*
|
||||
Custom Google Test assertions.
|
||||
|
||||
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++ - custom Google Test assertions
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "gtest-extra.h"
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
|
||||
using fmt::File;
|
||||
using fmt::file;
|
||||
|
||||
void OutputRedirect::flush() {
|
||||
#if EOF != -1
|
||||
@ -38,7 +18,7 @@ void OutputRedirect::flush() {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, fflush(file_));
|
||||
if (result != 0)
|
||||
throw fmt::SystemError(errno, "cannot flush stream");
|
||||
throw fmt::system_error(errno, "cannot flush stream");
|
||||
}
|
||||
|
||||
void OutputRedirect::restore() {
|
||||
@ -50,14 +30,14 @@ void OutputRedirect::restore() {
|
||||
original_.close();
|
||||
}
|
||||
|
||||
OutputRedirect::OutputRedirect(FILE *file) : file_(file) {
|
||||
OutputRedirect::OutputRedirect(FILE *f) : file_(f) {
|
||||
flush();
|
||||
int fd = FMT_POSIX(fileno(file));
|
||||
// Create a File object referring to the original file.
|
||||
original_ = File::dup(fd);
|
||||
int fd = FMT_POSIX(fileno(f));
|
||||
// Create a file object referring to the original file.
|
||||
original_ = file::dup(fd);
|
||||
// Create a pipe.
|
||||
File write_end;
|
||||
File::pipe(read_end_, write_end);
|
||||
file write_end;
|
||||
file::pipe(read_end_, write_end);
|
||||
// Connect the passed FILE object to the write end of the pipe.
|
||||
write_end.dup2(fd);
|
||||
}
|
||||
@ -80,23 +60,22 @@ std::string OutputRedirect::restore_and_read() {
|
||||
return content; // Already read.
|
||||
enum { BUFFER_SIZE = 4096 };
|
||||
char buffer[BUFFER_SIZE];
|
||||
std::streamsize count = 0;
|
||||
std::size_t count = 0;
|
||||
do {
|
||||
count = read_end_.read(buffer, BUFFER_SIZE);
|
||||
content.append(buffer, static_cast<std::size_t>(count));
|
||||
content.append(buffer, count);
|
||||
} while (count != 0);
|
||||
read_end_.close();
|
||||
return content;
|
||||
}
|
||||
|
||||
std::string read(File &f, std::size_t count) {
|
||||
std::string read(file &f, std::size_t count) {
|
||||
std::string buffer(count, '\0');
|
||||
std::streamsize n = 0;
|
||||
std::size_t offset = 0;
|
||||
std::size_t n = 0, offset = 0;
|
||||
do {
|
||||
n = f.read(&buffer[offset], count - offset);
|
||||
// We can't read more than size_t bytes since count has type size_t.
|
||||
offset += static_cast<std::size_t>(n);
|
||||
offset += n;
|
||||
} while (offset < count && n != 0);
|
||||
buffer.resize(offset);
|
||||
return buffer;
|
||||
@ -104,8 +83,8 @@ std::string read(File &f, std::size_t count) {
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
|
||||
std::string format_system_error(int error_code, fmt::StringRef message) {
|
||||
fmt::MemoryWriter out;
|
||||
fmt::internal::format_system_error(out, error_code, message);
|
||||
return out.str();
|
||||
std::string format_system_error(int error_code, fmt::string_view message) {
|
||||
fmt::memory_buffer out;
|
||||
format_system_error(out, error_code, message);
|
||||
return to_string(out);
|
||||
}
|
||||
|
@ -1,44 +1,24 @@
|
||||
/*
|
||||
Custom Google Test assertions.
|
||||
|
||||
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++ - custom Google Test assertions
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_GTEST_EXTRA_H_
|
||||
#define FMT_GTEST_EXTRA_H_
|
||||
|
||||
#include <string>
|
||||
#include <gtest/gtest.h>
|
||||
#include "gmock.h"
|
||||
|
||||
#include "format.h"
|
||||
#include "fmt/core.h"
|
||||
|
||||
#ifndef FMT_USE_FILE_DESCRIPTORS
|
||||
# define FMT_USE_FILE_DESCRIPTORS 0
|
||||
#endif
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
# include "posix.h"
|
||||
# include "fmt/posix.h"
|
||||
#endif
|
||||
|
||||
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
||||
@ -81,10 +61,10 @@
|
||||
FMT_TEST_THROW_(statement, expected_exception, \
|
||||
expected_message, GTEST_NONFATAL_FAILURE_)
|
||||
|
||||
std::string format_system_error(int error_code, fmt::StringRef message);
|
||||
std::string format_system_error(int error_code, fmt::string_view message);
|
||||
|
||||
#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \
|
||||
EXPECT_THROW_MSG(statement, fmt::SystemError, \
|
||||
EXPECT_THROW_MSG(statement, fmt::system_error, \
|
||||
format_system_error(error_code, message))
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
@ -94,8 +74,8 @@ std::string format_system_error(int error_code, fmt::StringRef message);
|
||||
class OutputRedirect {
|
||||
private:
|
||||
FILE *file_;
|
||||
fmt::File original_; // Original file passed to redirector.
|
||||
fmt::File read_end_; // Read end of the pipe where the output is redirected.
|
||||
fmt::file original_; // Original file passed to redirector.
|
||||
fmt::file read_end_; // Read end of the pipe where the output is redirected.
|
||||
|
||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(OutputRedirect);
|
||||
|
||||
@ -165,11 +145,17 @@ class SuppressAssert {
|
||||
EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message)
|
||||
|
||||
// Attempts to read count characters from a file.
|
||||
std::string read(fmt::File &f, std::size_t count);
|
||||
std::string read(fmt::file &f, std::size_t count);
|
||||
|
||||
#define EXPECT_READ(file, expected_content) \
|
||||
EXPECT_EQ(expected_content, read(file, std::strlen(expected_content)))
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
|
||||
template <typename Mock>
|
||||
struct ScopedMock : testing::StrictMock<Mock> {
|
||||
ScopedMock() { Mock::instance = this; }
|
||||
~ScopedMock() { Mock::instance = nullptr; }
|
||||
};
|
||||
|
||||
#endif // FMT_GTEST_EXTRA_H_
|
||||
|
@ -2823,7 +2823,11 @@ inline int IsATTY(int /* fd */) { return 0; }
|
||||
inline int IsATTY(int fd) { return _isatty(fd); }
|
||||
# endif // GTEST_OS_WINDOWS_MOBILE
|
||||
inline int StrCaseCmp(const char* s1, const char* s2) {
|
||||
return _stricmp(s1, s2);
|
||||
# if _EMULATE_GLIBC
|
||||
return strcasecmp(s1, s2);
|
||||
# else
|
||||
return _stricmp(s1, s2);
|
||||
# endif
|
||||
}
|
||||
inline char* StrDup(const char* src) { return _strdup(src); }
|
||||
# endif // __BORLANDC__
|
@ -1,28 +1,3 @@
|
||||
/*
|
||||
Header-only configuration test
|
||||
// Header-only configuration test
|
||||
|
||||
Copyright (c) 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.
|
||||
*/
|
||||
|
||||
#include "format.h"
|
||||
#include "fmt/core.h"
|
||||
|
@ -1,28 +1,3 @@
|
||||
/*
|
||||
Additional translation unit for the header-only configuration test
|
||||
// Additional translation unit for the header-only configuration test
|
||||
|
||||
Copyright (c) 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.
|
||||
*/
|
||||
|
||||
#include "format.h"
|
||||
#include "fmt/core.h"
|
||||
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
Tests of variadic function emulation macros.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#define FMT_USE_VARIADIC_TEMPLATES 0
|
||||
#include "format.h"
|
||||
|
||||
#define IDENTITY(x) x
|
||||
|
||||
TEST(UtilTest, Gen) {
|
||||
int values[] = {FMT_GEN(10, IDENTITY)};
|
||||
for (int i = 0; i < 10; ++i)
|
||||
EXPECT_EQ(i, values[i]);
|
||||
}
|
||||
|
||||
#define MAKE_PAIR(x, y) std::make_pair(x, y)
|
||||
|
||||
TEST(UtilTest, ForEach) {
|
||||
std::pair<char, int> values[] = {
|
||||
FMT_FOR_EACH10(MAKE_PAIR, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')
|
||||
};
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_EQ('a' + i, values[i].first);
|
||||
EXPECT_EQ(i, values[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UtilTest, NArg) {
|
||||
EXPECT_EQ(1, FMT_NARG(a));
|
||||
EXPECT_EQ(2, FMT_NARG(a, b));
|
||||
EXPECT_EQ(3, FMT_NARG(a, b, c));
|
||||
EXPECT_EQ(4, FMT_NARG(a, b, c, d));
|
||||
EXPECT_EQ(5, FMT_NARG(a, b, c, d, e));
|
||||
EXPECT_EQ(6, FMT_NARG(a, b, c, d, e, f));
|
||||
EXPECT_EQ(7, FMT_NARG(a, b, c, d, e, f, g));
|
||||
EXPECT_EQ(8, FMT_NARG(a, b, c, d, e, f, g, h));
|
||||
EXPECT_EQ(9, FMT_NARG(a, b, c, d, e, f, g, h, i));
|
||||
EXPECT_EQ(10, FMT_NARG(a, b, c, d, e, f, g, h, i, j));
|
||||
}
|
||||
|
||||
int result;
|
||||
|
||||
#define MAKE_TEST(func) \
|
||||
void func(const char *format, const fmt::ArgList &args) { \
|
||||
result = 0; \
|
||||
for (unsigned i = 0; args[i].type; ++i) \
|
||||
result += args[i].int_value; \
|
||||
}
|
||||
|
||||
MAKE_TEST(test_func)
|
||||
|
||||
typedef char Char;
|
||||
FMT_WRAP1(test_func, const char *, 1)
|
||||
|
||||
TEST(UtilTest, Wrap1) {
|
||||
result = 0;
|
||||
test_func("", 42);
|
||||
EXPECT_EQ(42, result);
|
||||
}
|
||||
|
||||
MAKE_TEST(test_variadic_void)
|
||||
FMT_VARIADIC_VOID(test_variadic_void, const char *)
|
||||
|
||||
TEST(UtilTest, VariadicVoid) {
|
||||
result = 0;
|
||||
test_variadic_void("", 10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
|
||||
EXPECT_EQ(550, result);
|
||||
}
|
||||
|
||||
template <int>
|
||||
struct S {};
|
||||
|
||||
#define GET_TYPE(n) S<n>
|
||||
|
||||
int test_variadic(FMT_GEN(10, GET_TYPE), const fmt::ArgList &args) { \
|
||||
int result = 0; \
|
||||
for (unsigned i = 0; args[i].type; ++i) \
|
||||
result += args[i].int_value; \
|
||||
return result;
|
||||
}
|
||||
FMT_VARIADIC(int, test_variadic,
|
||||
S<0>, S<1>, S<2>, S<3>, S<4>, S<5>, S<6>, S<7>, S<8>, S<9>)
|
||||
|
||||
#define MAKE_ARG(n) S<n>()
|
||||
|
||||
TEST(UtilTest, Variadic) {
|
||||
EXPECT_EQ(550, test_variadic(FMT_GEN(10, MAKE_ARG),
|
||||
10, 20, 30, 40, 50, 60, 70, 80, 90, 100));
|
||||
}
|
@ -1,34 +1,14 @@
|
||||
/*
|
||||
Mock allocator.
|
||||
|
||||
Copyright (c) 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++ - mock allocator
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_MOCK_ALLOCATOR_H_
|
||||
#define FMT_MOCK_ALLOCATOR_H_
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gmock.h"
|
||||
|
||||
template <typename T>
|
||||
class MockAllocator {
|
||||
@ -48,7 +28,7 @@ class AllocatorRef {
|
||||
public:
|
||||
typedef typename Allocator::value_type value_type;
|
||||
|
||||
explicit AllocatorRef(Allocator *alloc = 0) : alloc_(alloc) {}
|
||||
explicit AllocatorRef(Allocator *alloc = nullptr) : alloc_(alloc) {}
|
||||
|
||||
AllocatorRef(const AllocatorRef &other) : alloc_(other.alloc_) {}
|
||||
|
||||
@ -61,7 +41,7 @@ class AllocatorRef {
|
||||
private:
|
||||
void move(AllocatorRef &other) {
|
||||
alloc_ = other.alloc_;
|
||||
other.alloc_ = 0;
|
||||
other.alloc_ = nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
@ -78,7 +58,9 @@ class AllocatorRef {
|
||||
|
||||
Allocator *get() const { return alloc_; }
|
||||
|
||||
value_type* allocate(std::size_t n) { return alloc_->allocate(n); }
|
||||
value_type* allocate(std::size_t n) {
|
||||
return fmt::internal::allocate(*alloc_, n);
|
||||
}
|
||||
void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); }
|
||||
};
|
||||
|
||||
|
171
test/ostream-test.cc
Normal file
171
test/ostream-test.cc
Normal file
@ -0,0 +1,171 @@
|
||||
// Formatting library for C++ - std::ostream support tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/ostream.h"
|
||||
|
||||
#include <sstream>
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
using fmt::format;
|
||||
using fmt::format_error;
|
||||
|
||||
static std::ostream &operator<<(std::ostream &os, const Date &d) {
|
||||
os << d.year() << '-' << d.month() << '-' << d.day();
|
||||
return os;
|
||||
}
|
||||
|
||||
static std::wostream &operator<<(std::wostream &os, const Date &d) {
|
||||
os << d.year() << L'-' << d.month() << L'-' << d.day();
|
||||
return os;
|
||||
}
|
||||
|
||||
enum TestEnum {};
|
||||
static std::ostream &operator<<(std::ostream &os, TestEnum) {
|
||||
return os << "TestEnum";
|
||||
}
|
||||
|
||||
static std::wostream &operator<<(std::wostream &os, TestEnum) {
|
||||
return os << L"TestEnum";
|
||||
}
|
||||
|
||||
enum TestEnum2 {A};
|
||||
|
||||
TEST(OStreamTest, Enum) {
|
||||
EXPECT_FALSE((fmt::internal::convert_to_int<TestEnum, char>::value));
|
||||
EXPECT_EQ("TestEnum", fmt::format("{}", TestEnum()));
|
||||
EXPECT_EQ("0", fmt::format("{}", A));
|
||||
EXPECT_FALSE((fmt::internal::convert_to_int<TestEnum, wchar_t>::value));
|
||||
EXPECT_EQ(L"TestEnum", fmt::format(L"{}", TestEnum()));
|
||||
EXPECT_EQ(L"0", fmt::format(L"{}", A));
|
||||
}
|
||||
|
||||
typedef fmt::back_insert_range<fmt::internal::buffer> range;
|
||||
|
||||
struct test_arg_formatter: fmt::arg_formatter<range> {
|
||||
test_arg_formatter(fmt::format_context &ctx, fmt::format_specs &s)
|
||||
: fmt::arg_formatter<range>(ctx, s) {}
|
||||
};
|
||||
|
||||
TEST(OStreamTest, CustomArg) {
|
||||
fmt::memory_buffer buffer;
|
||||
fmt::internal::buffer &base = buffer;
|
||||
fmt::format_context ctx(std::back_inserter(base), "", fmt::format_args());
|
||||
fmt::format_specs spec;
|
||||
test_arg_formatter af(ctx, spec);
|
||||
visit(af, fmt::internal::make_arg<fmt::format_context>(TestEnum()));
|
||||
EXPECT_EQ("TestEnum", std::string(buffer.data(), buffer.size()));
|
||||
}
|
||||
|
||||
TEST(OStreamTest, Format) {
|
||||
EXPECT_EQ("a string", format("{0}", TestString("a string")));
|
||||
std::string s = format("The date is {0}", Date(2012, 12, 9));
|
||||
EXPECT_EQ("The date is 2012-12-9", s);
|
||||
Date date(2012, 12, 9);
|
||||
EXPECT_EQ(L"The date is 2012-12-9",
|
||||
format(L"The date is {0}", Date(2012, 12, 9)));
|
||||
}
|
||||
|
||||
TEST(OStreamTest, FormatSpecs) {
|
||||
EXPECT_EQ("def ", format("{0:<5}", TestString("def")));
|
||||
EXPECT_EQ(" def", format("{0:>5}", TestString("def")));
|
||||
EXPECT_THROW_MSG(format("{0:=5}", TestString("def")),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_EQ(" def ", format("{0:^5}", TestString("def")));
|
||||
EXPECT_EQ("def**", format("{0:*<5}", TestString("def")));
|
||||
EXPECT_THROW_MSG(format("{0:+}", TestString()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG(format("{0:-}", TestString()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG(format("{0: }", TestString()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG(format("{0:#}", TestString()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG(format("{0:05}", TestString()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_EQ("test ", format("{0:13}", TestString("test")));
|
||||
EXPECT_EQ("test ", format("{0:{1}}", TestString("test"), 13));
|
||||
EXPECT_EQ("te", format("{0:.2}", TestString("test")));
|
||||
EXPECT_EQ("te", format("{0:.{1}}", TestString("test"), 2));
|
||||
}
|
||||
|
||||
struct EmptyTest {};
|
||||
static std::ostream &operator<<(std::ostream &os, EmptyTest) {
|
||||
return os << "";
|
||||
}
|
||||
|
||||
TEST(OStreamTest, EmptyCustomOutput) {
|
||||
EXPECT_EQ("", fmt::format("{}", EmptyTest()));
|
||||
}
|
||||
|
||||
TEST(OStreamTest, Print) {
|
||||
std::ostringstream os;
|
||||
fmt::print(os, "Don't {}!", "panic");
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
std::wostringstream wos;
|
||||
fmt::print(wos, L"Don't {}!", L"panic");
|
||||
EXPECT_EQ(L"Don't panic!", wos.str());
|
||||
}
|
||||
|
||||
TEST(OStreamTest, WriteToOStream) {
|
||||
std::ostringstream os;
|
||||
fmt::memory_buffer buffer;
|
||||
const char *foo = "foo";
|
||||
buffer.append(foo, foo + std::strlen(foo));
|
||||
fmt::internal::write(os, buffer);
|
||||
EXPECT_EQ("foo", os.str());
|
||||
}
|
||||
|
||||
TEST(OStreamTest, WriteToOStreamMaxSize) {
|
||||
std::size_t max_size = std::numeric_limits<std::size_t>::max();
|
||||
std::streamsize max_streamsize = std::numeric_limits<std::streamsize>::max();
|
||||
if (max_size <= fmt::internal::to_unsigned(max_streamsize))
|
||||
return;
|
||||
|
||||
struct test_buffer : fmt::internal::buffer {
|
||||
explicit test_buffer(std::size_t size) { resize(size); }
|
||||
void grow(std::size_t) {}
|
||||
} buffer(max_size);
|
||||
|
||||
struct mock_streambuf : std::streambuf {
|
||||
MOCK_METHOD2(xsputn, std::streamsize (const void *s, std::streamsize n));
|
||||
std::streamsize xsputn(const char *s, std::streamsize n) {
|
||||
const void *v = s;
|
||||
return xsputn(v, n);
|
||||
}
|
||||
} streambuf;
|
||||
|
||||
struct test_ostream : std::ostream {
|
||||
explicit test_ostream(mock_streambuf &buffer) : std::ostream(&buffer) {}
|
||||
} os(streambuf);
|
||||
|
||||
testing::InSequence sequence;
|
||||
const char *data = nullptr;
|
||||
std::size_t size = max_size;
|
||||
do {
|
||||
typedef std::make_unsigned<std::streamsize>::type ustreamsize;
|
||||
ustreamsize n = std::min<ustreamsize>(
|
||||
size, fmt::internal::to_unsigned(max_streamsize));
|
||||
EXPECT_CALL(streambuf, xsputn(data, static_cast<std::streamsize>(n)))
|
||||
.WillOnce(testing::Return(max_streamsize));
|
||||
data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
fmt::internal::write(os, buffer);
|
||||
}
|
||||
|
||||
TEST(OStreamTest, Join) {
|
||||
int v[3] = {1, 2, 3};
|
||||
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", ")));
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
TEST(OStreamTest, ConstexprString) {
|
||||
EXPECT_EQ("42", format(fmt("{}"), std::string("42")));
|
||||
}
|
||||
#endif
|
@ -1,35 +1,17 @@
|
||||
/*
|
||||
Tests of the C++ interface to POSIX functions that require mocks
|
||||
|
||||
Copyright (c) 2012-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.
|
||||
*/
|
||||
// Tests of the C++ interface to POSIX functions that require mocks
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#ifdef _MSC_VER
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "posix-mock.h"
|
||||
#include "posix.cc"
|
||||
#include "../src/posix.cc"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@ -41,14 +23,18 @@
|
||||
# undef ERROR
|
||||
#endif
|
||||
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
using fmt::BufferedFile;
|
||||
using fmt::ErrorCode;
|
||||
using fmt::File;
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
using fmt::file;
|
||||
|
||||
using testing::internal::scoped_ptr;
|
||||
using testing::_;
|
||||
using testing::StrEq;
|
||||
using testing::Return;
|
||||
|
||||
namespace {
|
||||
int open_count;
|
||||
@ -147,7 +133,7 @@ int test::dup2(int fildes, int fildes2) {
|
||||
}
|
||||
|
||||
FILE *test::fdopen(int fildes, const char *mode) {
|
||||
EMULATE_EINTR(fdopen, 0);
|
||||
EMULATE_EINTR(fdopen, nullptr);
|
||||
return ::FMT_POSIX(fdopen(fildes, mode));
|
||||
}
|
||||
|
||||
@ -176,7 +162,7 @@ int test::pipe(int *pfds, unsigned psize, int textmode) {
|
||||
#endif
|
||||
|
||||
FILE *test::fopen(const char *filename, const char *mode) {
|
||||
EMULATE_EINTR(fopen, 0);
|
||||
EMULATE_EINTR(fopen, nullptr);
|
||||
return ::fopen(filename, mode);
|
||||
}
|
||||
|
||||
@ -209,17 +195,11 @@ int (test::fileno)(FILE *stream) {
|
||||
# define EXPECT_EQ_POSIX(expected, actual)
|
||||
#endif
|
||||
|
||||
void write_file(fmt::CStringRef filename, fmt::StringRef content) {
|
||||
fmt::BufferedFile f(filename, "w");
|
||||
static void write_file(fmt::cstring_view filename, fmt::string_view content) {
|
||||
fmt::buffered_file f(filename, "w");
|
||||
f.print("{}", content);
|
||||
}
|
||||
|
||||
TEST(UtilTest, StaticAssert) {
|
||||
FMT_STATIC_ASSERT(true, "success");
|
||||
// Static assertion failure is tested in compile-test because it causes
|
||||
// a compile-time error.
|
||||
}
|
||||
|
||||
TEST(UtilTest, GetPageSize) {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO si = {};
|
||||
@ -236,8 +216,8 @@ TEST(UtilTest, GetPageSize) {
|
||||
|
||||
TEST(FileTest, OpenRetry) {
|
||||
write_file("test", "there must be something here");
|
||||
scoped_ptr<File> f;
|
||||
EXPECT_RETRY(f.reset(new File("test", File::RDONLY)),
|
||||
scoped_ptr<file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new file("test", file::RDONLY)),
|
||||
open, "cannot open file test");
|
||||
#ifndef _WIN32
|
||||
char c = 0;
|
||||
@ -246,13 +226,13 @@ TEST(FileTest, OpenRetry) {
|
||||
}
|
||||
|
||||
TEST(FileTest, CloseNoRetryInDtor) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
scoped_ptr<File> f(new File(std::move(read_end)));
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
scoped_ptr<file> f(new file(std::move(read_end)));
|
||||
int saved_close_count = 0;
|
||||
EXPECT_WRITE(stderr, {
|
||||
close_count = 1;
|
||||
f.reset();
|
||||
f.reset(nullptr);
|
||||
saved_close_count = close_count;
|
||||
close_count = 0;
|
||||
}, format_system_error(EINTR, "cannot close file") + "\n");
|
||||
@ -260,8 +240,8 @@ TEST(FileTest, CloseNoRetryInDtor) {
|
||||
}
|
||||
|
||||
TEST(FileTest, CloseNoRetry) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
close_count = 1;
|
||||
EXPECT_SYSTEM_ERROR(read_end.close(), EINTR, "cannot close file");
|
||||
EXPECT_EQ(2, close_count);
|
||||
@ -271,16 +251,15 @@ TEST(FileTest, CloseNoRetry) {
|
||||
TEST(FileTest, Size) {
|
||||
std::string content = "top secret, destroy before reading";
|
||||
write_file("test", content);
|
||||
File f("test", File::RDONLY);
|
||||
file f("test", file::RDONLY);
|
||||
EXPECT_GE(f.size(), 0);
|
||||
fmt::ULongLong file_size = f.size();
|
||||
EXPECT_EQ(content.size(), file_size);
|
||||
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
|
||||
#ifdef _WIN32
|
||||
fmt::MemoryWriter message;
|
||||
fmt::memory_buffer message;
|
||||
fmt::internal::format_windows_error(
|
||||
message, ERROR_ACCESS_DENIED, "cannot get file size");
|
||||
fstat_sim = ERROR;
|
||||
EXPECT_THROW_MSG(f.size(), fmt::WindowsError, message.str());
|
||||
EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message));
|
||||
fstat_sim = NONE;
|
||||
#else
|
||||
f.close();
|
||||
@ -290,7 +269,7 @@ TEST(FileTest, Size) {
|
||||
|
||||
TEST(FileTest, MaxSize) {
|
||||
write_file("test", "");
|
||||
File f("test", File::RDONLY);
|
||||
file f("test", file::RDONLY);
|
||||
fstat_sim = MAX_SIZE;
|
||||
EXPECT_GE(f.size(), 0);
|
||||
EXPECT_EQ(max_file_size(), f.size());
|
||||
@ -298,23 +277,23 @@ TEST(FileTest, MaxSize) {
|
||||
}
|
||||
|
||||
TEST(FileTest, ReadRetry) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
enum { SIZE = 4 };
|
||||
write_end.write("test", SIZE);
|
||||
write_end.close();
|
||||
char buffer[SIZE];
|
||||
std::streamsize count = 0;
|
||||
std::size_t count = 0;
|
||||
EXPECT_RETRY(count = read_end.read(buffer, SIZE),
|
||||
read, "cannot read from file");
|
||||
EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
|
||||
}
|
||||
|
||||
TEST(FileTest, WriteRetry) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
enum { SIZE = 4 };
|
||||
std::streamsize count = 0;
|
||||
std::size_t count = 0;
|
||||
EXPECT_RETRY(count = write_end.write("test", SIZE),
|
||||
write, "cannot write to file");
|
||||
write_end.close();
|
||||
@ -329,29 +308,29 @@ TEST(FileTest, WriteRetry) {
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(FileTest, ConvertReadCount) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
char c;
|
||||
std::size_t size = UINT_MAX;
|
||||
if (sizeof(unsigned) != sizeof(std::size_t))
|
||||
++size;
|
||||
read_count = 1;
|
||||
read_nbyte = 0;
|
||||
EXPECT_THROW(read_end.read(&c, size), fmt::SystemError);
|
||||
EXPECT_THROW(read_end.read(&c, size), fmt::system_error);
|
||||
read_count = 0;
|
||||
EXPECT_EQ(UINT_MAX, read_nbyte);
|
||||
}
|
||||
|
||||
TEST(FileTest, ConvertWriteCount) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
char c;
|
||||
std::size_t size = UINT_MAX;
|
||||
if (sizeof(unsigned) != sizeof(std::size_t))
|
||||
++size;
|
||||
write_count = 1;
|
||||
write_nbyte = 0;
|
||||
EXPECT_THROW(write_end.write(&c, size), fmt::SystemError);
|
||||
EXPECT_THROW(write_end.write(&c, size), fmt::system_error);
|
||||
write_count = 0;
|
||||
EXPECT_EQ(UINT_MAX, write_nbyte);
|
||||
}
|
||||
@ -360,14 +339,14 @@ TEST(FileTest, ConvertWriteCount) {
|
||||
TEST(FileTest, DupNoRetry) {
|
||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||
dup_count = 1;
|
||||
EXPECT_SYSTEM_ERROR(File::dup(stdout_fd), EINTR,
|
||||
EXPECT_SYSTEM_ERROR(file::dup(stdout_fd), EINTR,
|
||||
fmt::format("cannot duplicate file descriptor {}", stdout_fd));
|
||||
dup_count = 0;
|
||||
}
|
||||
|
||||
TEST(FileTest, Dup2Retry) {
|
||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||
File f1 = File::dup(stdout_fd), f2 = File::dup(stdout_fd);
|
||||
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
|
||||
EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
|
||||
fmt::format("cannot duplicate file descriptor {} to {}",
|
||||
f1.descriptor(), f2.descriptor()));
|
||||
@ -375,8 +354,8 @@ TEST(FileTest, Dup2Retry) {
|
||||
|
||||
TEST(FileTest, Dup2NoExceptRetry) {
|
||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||
File f1 = File::dup(stdout_fd), f2 = File::dup(stdout_fd);
|
||||
ErrorCode ec;
|
||||
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
|
||||
error_code ec;
|
||||
dup2_count = 1;
|
||||
f1.dup2(f2.descriptor(), ec);
|
||||
#ifndef _WIN32
|
||||
@ -388,16 +367,16 @@ TEST(FileTest, Dup2NoExceptRetry) {
|
||||
}
|
||||
|
||||
TEST(FileTest, PipeNoRetry) {
|
||||
File read_end, write_end;
|
||||
file read_end, write_end;
|
||||
pipe_count = 1;
|
||||
EXPECT_SYSTEM_ERROR(
|
||||
File::pipe(read_end, write_end), EINTR, "cannot create pipe");
|
||||
file::pipe(read_end, write_end), EINTR, "cannot create pipe");
|
||||
pipe_count = 0;
|
||||
}
|
||||
|
||||
TEST(FileTest, FdopenNoRetry) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
fdopen_count = 1;
|
||||
EXPECT_SYSTEM_ERROR(read_end.fdopen("r"),
|
||||
EINTR, "cannot associate stream with file descriptor");
|
||||
@ -406,24 +385,24 @@ TEST(FileTest, FdopenNoRetry) {
|
||||
|
||||
TEST(BufferedFileTest, OpenRetry) {
|
||||
write_file("test", "there must be something here");
|
||||
scoped_ptr<BufferedFile> f;
|
||||
EXPECT_RETRY(f.reset(new BufferedFile("test", "r")),
|
||||
scoped_ptr<buffered_file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new buffered_file("test", "r")),
|
||||
fopen, "cannot open file test");
|
||||
#ifndef _WIN32
|
||||
char c = 0;
|
||||
if (fread(&c, 1, 1, f->get()) < 1)
|
||||
throw fmt::SystemError(errno, "fread failed");
|
||||
throw fmt::system_error(errno, "fread failed");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
scoped_ptr<BufferedFile> f(new BufferedFile(read_end.fdopen("r")));
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
scoped_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
|
||||
int saved_fclose_count = 0;
|
||||
EXPECT_WRITE(stderr, {
|
||||
fclose_count = 1;
|
||||
f.reset();
|
||||
f.reset(nullptr);
|
||||
saved_fclose_count = fclose_count;
|
||||
fclose_count = 0;
|
||||
}, format_system_error(EINTR, "cannot close file") + "\n");
|
||||
@ -431,9 +410,9 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, CloseNoRetry) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
BufferedFile f = read_end.fdopen("r");
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
buffered_file f = read_end.fdopen("r");
|
||||
fclose_count = 1;
|
||||
EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file");
|
||||
EXPECT_EQ(2, fclose_count);
|
||||
@ -441,11 +420,120 @@ TEST(BufferedFileTest, CloseNoRetry) {
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, FilenoNoRetry) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
BufferedFile f = read_end.fdopen("r");
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
buffered_file f = read_end.fdopen("r");
|
||||
fileno_count = 1;
|
||||
EXPECT_SYSTEM_ERROR((f.fileno)(), EINTR, "cannot get file descriptor");
|
||||
EXPECT_EQ(2, fileno_count);
|
||||
fileno_count = 0;
|
||||
}
|
||||
|
||||
struct TestMock {
|
||||
static TestMock *instance;
|
||||
} *TestMock::instance;
|
||||
|
||||
TEST(ScopedMock, Scope) {
|
||||
{
|
||||
ScopedMock<TestMock> mock;
|
||||
EXPECT_EQ(&mock, TestMock::instance);
|
||||
TestMock © = mock;
|
||||
static_cast<void>(copy);
|
||||
}
|
||||
EXPECT_EQ(nullptr, TestMock::instance);
|
||||
}
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
|
||||
typedef fmt::Locale::Type LocaleType;
|
||||
|
||||
struct LocaleMock {
|
||||
static LocaleMock *instance;
|
||||
MOCK_METHOD3(newlocale, LocaleType (int category_mask, const char *locale,
|
||||
LocaleType base));
|
||||
MOCK_METHOD1(freelocale, void (LocaleType locale));
|
||||
|
||||
MOCK_METHOD3(strtod_l, double (const char *nptr, char **endptr,
|
||||
LocaleType locale));
|
||||
} *LocaleMock::instance;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4273)
|
||||
|
||||
_locale_t _create_locale(int category, const char *locale) {
|
||||
return LocaleMock::instance->newlocale(category, locale, 0);
|
||||
}
|
||||
|
||||
void _free_locale(_locale_t locale) {
|
||||
LocaleMock::instance->freelocale(locale);
|
||||
}
|
||||
|
||||
double _strtod_l(const char *nptr, char **endptr, _locale_t locale) {
|
||||
return LocaleMock::instance->strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#if defined(__THROW) && FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408
|
||||
#define FMT_LOCALE_THROW __THROW
|
||||
#else
|
||||
#define FMT_LOCALE_THROW
|
||||
#endif
|
||||
|
||||
LocaleType newlocale(int category_mask, const char *locale, LocaleType base) FMT_LOCALE_THROW {
|
||||
return LocaleMock::instance->newlocale(category_mask, locale, base);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 1200002)
|
||||
typedef int FreeLocaleResult;
|
||||
#else
|
||||
typedef void FreeLocaleResult;
|
||||
#endif
|
||||
|
||||
FreeLocaleResult freelocale(LocaleType locale) FMT_LOCALE_THROW {
|
||||
LocaleMock::instance->freelocale(locale);
|
||||
return FreeLocaleResult();
|
||||
}
|
||||
|
||||
double strtod_l(const char *nptr, char **endptr, LocaleType locale) FMT_LOCALE_THROW {
|
||||
return LocaleMock::instance->strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
|
||||
#undef FMT_LOCALE_THROW
|
||||
|
||||
TEST(LocaleTest, LocaleMock) {
|
||||
ScopedMock<LocaleMock> mock;
|
||||
LocaleType locale = reinterpret_cast<LocaleType>(11);
|
||||
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
||||
newlocale(222, "foo", locale);
|
||||
}
|
||||
|
||||
TEST(LocaleTest, Locale) {
|
||||
#ifndef LC_NUMERIC_MASK
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
#endif
|
||||
ScopedMock<LocaleMock> mock;
|
||||
LocaleType impl = reinterpret_cast<LocaleType>(42);
|
||||
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
|
||||
.WillOnce(Return(impl));
|
||||
EXPECT_CALL(mock, freelocale(impl));
|
||||
fmt::Locale locale;
|
||||
EXPECT_EQ(impl, locale.get());
|
||||
}
|
||||
|
||||
TEST(LocaleTest, Strtod) {
|
||||
ScopedMock<LocaleMock> mock;
|
||||
EXPECT_CALL(mock, newlocale(_, _, _))
|
||||
.WillOnce(Return(reinterpret_cast<LocaleType>(42)));
|
||||
EXPECT_CALL(mock, freelocale(_));
|
||||
fmt::Locale locale;
|
||||
const char *str = "4.2";
|
||||
char end = 'x';
|
||||
EXPECT_CALL(mock, strtod_l(str, _, locale.get()))
|
||||
.WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777)));
|
||||
EXPECT_EQ(777, locale.strtod(str));
|
||||
EXPECT_EQ(&end, str);
|
||||
}
|
||||
|
||||
#endif // FMT_LOCALE
|
||||
|
@ -1,29 +1,9 @@
|
||||
/*
|
||||
Mocks of POSIX functions
|
||||
|
||||
Copyright (c) 2012-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.
|
||||
*/
|
||||
// Formatting library for C++ - mocks of POSIX functions
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_POSIX_TEST_H
|
||||
#define FMT_POSIX_TEST_H
|
||||
@ -34,6 +14,7 @@
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <sys/param.h> // for FreeBSD version
|
||||
# include <sys/types.h> // for ssize_t
|
||||
#endif
|
||||
|
||||
|
@ -1,53 +1,34 @@
|
||||
/*
|
||||
Tests of the C++ interface to POSIX functions
|
||||
|
||||
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.
|
||||
*/
|
||||
// Formatting library for C++ - tests of the C++ interface to POSIX functions
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <cstdlib> // std::exit
|
||||
#include <cstring>
|
||||
|
||||
#include "fmt/posix.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "posix.h"
|
||||
#include "util.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
#endif
|
||||
|
||||
using fmt::BufferedFile;
|
||||
using fmt::ErrorCode;
|
||||
using fmt::File;
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
using fmt::file;
|
||||
|
||||
using testing::internal::scoped_ptr;
|
||||
|
||||
// Checks if the file is open by reading one character from it.
|
||||
bool isopen(int fd) {
|
||||
static bool isopen(int fd) {
|
||||
char buffer;
|
||||
return FMT_POSIX(read(fd, &buffer, 1)) == 1;
|
||||
}
|
||||
|
||||
bool isclosed(int fd) {
|
||||
static bool isclosed(int fd) {
|
||||
char buffer;
|
||||
std::streamsize result = 0;
|
||||
SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1)));
|
||||
@ -55,74 +36,74 @@ bool isclosed(int fd) {
|
||||
}
|
||||
|
||||
// Opens a file for reading.
|
||||
File open_file() {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
static file open_file() {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT));
|
||||
write_end.close();
|
||||
return read_end;
|
||||
}
|
||||
|
||||
// Attempts to write a string to a file.
|
||||
void write(File &f, fmt::StringRef s) {
|
||||
static void write(file &f, fmt::string_view s) {
|
||||
std::size_t num_chars_left = s.size();
|
||||
const char *ptr = s.data();
|
||||
do {
|
||||
std::streamsize count = f.write(ptr, num_chars_left);
|
||||
std::size_t count = f.write(ptr, num_chars_left);
|
||||
ptr += count;
|
||||
// We can't write more than size_t bytes since num_chars_left
|
||||
// has type size_t.
|
||||
num_chars_left -= static_cast<std::size_t>(count);
|
||||
num_chars_left -= count;
|
||||
} while (num_chars_left != 0);
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, DefaultCtor) {
|
||||
BufferedFile f;
|
||||
EXPECT_TRUE(f.get() == 0);
|
||||
buffered_file f;
|
||||
EXPECT_TRUE(f.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, MoveCtor) {
|
||||
BufferedFile bf = open_buffered_file();
|
||||
buffered_file bf = open_buffered_file();
|
||||
FILE *fp = bf.get();
|
||||
EXPECT_TRUE(fp != 0);
|
||||
BufferedFile bf2(std::move(bf));
|
||||
EXPECT_TRUE(fp != nullptr);
|
||||
buffered_file bf2(std::move(bf));
|
||||
EXPECT_EQ(fp, bf2.get());
|
||||
EXPECT_TRUE(bf.get() == 0);
|
||||
EXPECT_TRUE(bf.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, MoveAssignment) {
|
||||
BufferedFile bf = open_buffered_file();
|
||||
buffered_file bf = open_buffered_file();
|
||||
FILE *fp = bf.get();
|
||||
EXPECT_TRUE(fp != 0);
|
||||
BufferedFile bf2;
|
||||
EXPECT_TRUE(fp != nullptr);
|
||||
buffered_file bf2;
|
||||
bf2 = std::move(bf);
|
||||
EXPECT_EQ(fp, bf2.get());
|
||||
EXPECT_TRUE(bf.get() == 0);
|
||||
EXPECT_TRUE(bf.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, MoveAssignmentClosesFile) {
|
||||
BufferedFile bf = open_buffered_file();
|
||||
BufferedFile bf2 = open_buffered_file();
|
||||
buffered_file bf = open_buffered_file();
|
||||
buffered_file bf2 = open_buffered_file();
|
||||
int old_fd = bf2.fileno();
|
||||
bf2 = std::move(bf);
|
||||
EXPECT_TRUE(isclosed(old_fd));
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, MoveFromTemporaryInCtor) {
|
||||
FILE *fp = 0;
|
||||
BufferedFile f(open_buffered_file(&fp));
|
||||
FILE *fp = nullptr;
|
||||
buffered_file f(open_buffered_file(&fp));
|
||||
EXPECT_EQ(fp, f.get());
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, MoveFromTemporaryInAssignment) {
|
||||
FILE *fp = 0;
|
||||
BufferedFile f;
|
||||
FILE *fp = nullptr;
|
||||
buffered_file f;
|
||||
f = open_buffered_file(&fp);
|
||||
EXPECT_EQ(fp, f.get());
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) {
|
||||
BufferedFile f = open_buffered_file();
|
||||
buffered_file f = open_buffered_file();
|
||||
int old_fd = f.fileno();
|
||||
f = open_buffered_file();
|
||||
EXPECT_TRUE(isclosed(old_fd));
|
||||
@ -131,60 +112,60 @@ TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) {
|
||||
TEST(BufferedFileTest, CloseFileInDtor) {
|
||||
int fd = 0;
|
||||
{
|
||||
BufferedFile f = open_buffered_file();
|
||||
buffered_file f = open_buffered_file();
|
||||
fd = f.fileno();
|
||||
}
|
||||
EXPECT_TRUE(isclosed(fd));
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, CloseErrorInDtor) {
|
||||
scoped_ptr<BufferedFile> f(new BufferedFile(open_buffered_file()));
|
||||
scoped_ptr<buffered_file> f(new buffered_file(open_buffered_file()));
|
||||
EXPECT_WRITE(stderr, {
|
||||
// The close function must be called inside EXPECT_WRITE, otherwise
|
||||
// the system may recycle closed file descriptor when redirecting the
|
||||
// output in EXPECT_STDERR and the second close will break output
|
||||
// redirection.
|
||||
FMT_POSIX(close(f->fileno()));
|
||||
SUPPRESS_ASSERT(f.reset());
|
||||
SUPPRESS_ASSERT(f.reset(nullptr));
|
||||
}, format_system_error(EBADF, "cannot close file") + "\n");
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, Close) {
|
||||
BufferedFile f = open_buffered_file();
|
||||
buffered_file f = open_buffered_file();
|
||||
int fd = f.fileno();
|
||||
f.close();
|
||||
EXPECT_TRUE(f.get() == 0);
|
||||
EXPECT_TRUE(f.get() == nullptr);
|
||||
EXPECT_TRUE(isclosed(fd));
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, CloseError) {
|
||||
BufferedFile f = open_buffered_file();
|
||||
buffered_file f = open_buffered_file();
|
||||
FMT_POSIX(close(f.fileno()));
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
|
||||
EXPECT_TRUE(f.get() == 0);
|
||||
EXPECT_TRUE(f.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, Fileno) {
|
||||
BufferedFile f;
|
||||
buffered_file f;
|
||||
#ifndef __COVERITY__
|
||||
// fileno on a null FILE pointer either crashes or returns an error.
|
||||
// Disable Coverity because this is intentional.
|
||||
EXPECT_DEATH_IF_SUPPORTED({
|
||||
try {
|
||||
f.fileno();
|
||||
} catch (fmt::SystemError) {
|
||||
} catch (const fmt::system_error&) {
|
||||
std::exit(1);
|
||||
}
|
||||
}, "");
|
||||
#endif
|
||||
f = open_buffered_file();
|
||||
EXPECT_TRUE(f.fileno() != -1);
|
||||
File copy = File::dup(f.fileno());
|
||||
file copy = file::dup(f.fileno());
|
||||
EXPECT_READ(copy, FILE_CONTENT);
|
||||
}
|
||||
|
||||
TEST(FileTest, DefaultCtor) {
|
||||
File f;
|
||||
file f;
|
||||
EXPECT_EQ(-1, f.descriptor());
|
||||
}
|
||||
|
||||
@ -192,64 +173,64 @@ TEST(FileTest, OpenBufferedFileInCtor) {
|
||||
FILE *fp = safe_fopen("test-file", "w");
|
||||
std::fputs(FILE_CONTENT, fp);
|
||||
std::fclose(fp);
|
||||
File f("test-file", File::RDONLY);
|
||||
file f("test-file", file::RDONLY);
|
||||
ASSERT_TRUE(isopen(f.descriptor()));
|
||||
}
|
||||
|
||||
TEST(FileTest, OpenBufferedFileError) {
|
||||
EXPECT_SYSTEM_ERROR(File("nonexistent", File::RDONLY),
|
||||
EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY),
|
||||
ENOENT, "cannot open file nonexistent");
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveCtor) {
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
int fd = f.descriptor();
|
||||
EXPECT_NE(-1, fd);
|
||||
File f2(std::move(f));
|
||||
file f2(std::move(f));
|
||||
EXPECT_EQ(fd, f2.descriptor());
|
||||
EXPECT_EQ(-1, f.descriptor());
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveAssignment) {
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
int fd = f.descriptor();
|
||||
EXPECT_NE(-1, fd);
|
||||
File f2;
|
||||
file f2;
|
||||
f2 = std::move(f);
|
||||
EXPECT_EQ(fd, f2.descriptor());
|
||||
EXPECT_EQ(-1, f.descriptor());
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveAssignmentClosesFile) {
|
||||
File f = open_file();
|
||||
File f2 = open_file();
|
||||
file f = open_file();
|
||||
file f2 = open_file();
|
||||
int old_fd = f2.descriptor();
|
||||
f2 = std::move(f);
|
||||
EXPECT_TRUE(isclosed(old_fd));
|
||||
}
|
||||
|
||||
File OpenBufferedFile(int &fd) {
|
||||
File f = open_file();
|
||||
static file OpenBufferedFile(int &fd) {
|
||||
file f = open_file();
|
||||
fd = f.descriptor();
|
||||
return std::move(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveFromTemporaryInCtor) {
|
||||
int fd = 0xdeadbeef;
|
||||
File f(OpenBufferedFile(fd));
|
||||
int fd = 0xdead;
|
||||
file f(OpenBufferedFile(fd));
|
||||
EXPECT_EQ(fd, f.descriptor());
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveFromTemporaryInAssignment) {
|
||||
int fd = 0xdeadbeef;
|
||||
File f;
|
||||
int fd = 0xdead;
|
||||
file f;
|
||||
f = OpenBufferedFile(fd);
|
||||
EXPECT_EQ(fd, f.descriptor());
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) {
|
||||
int fd = 0xdeadbeef;
|
||||
File f = open_file();
|
||||
int fd = 0xdead;
|
||||
file f = open_file();
|
||||
int old_fd = f.descriptor();
|
||||
f = OpenBufferedFile(fd);
|
||||
EXPECT_TRUE(isclosed(old_fd));
|
||||
@ -258,26 +239,26 @@ TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) {
|
||||
TEST(FileTest, CloseFileInDtor) {
|
||||
int fd = 0;
|
||||
{
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
fd = f.descriptor();
|
||||
}
|
||||
EXPECT_TRUE(isclosed(fd));
|
||||
}
|
||||
|
||||
TEST(FileTest, CloseErrorInDtor) {
|
||||
scoped_ptr<File> f(new File(open_file()));
|
||||
scoped_ptr<file> f(new file(open_file()));
|
||||
EXPECT_WRITE(stderr, {
|
||||
// The close function must be called inside EXPECT_WRITE, otherwise
|
||||
// the system may recycle closed file descriptor when redirecting the
|
||||
// output in EXPECT_STDERR and the second close will break output
|
||||
// redirection.
|
||||
FMT_POSIX(close(f->descriptor()));
|
||||
SUPPRESS_ASSERT(f.reset());
|
||||
SUPPRESS_ASSERT(f.reset(nullptr));
|
||||
}, format_system_error(EBADF, "cannot close file") + "\n");
|
||||
}
|
||||
|
||||
TEST(FileTest, Close) {
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
int fd = f.descriptor();
|
||||
f.close();
|
||||
EXPECT_EQ(-1, f.descriptor());
|
||||
@ -285,19 +266,19 @@ TEST(FileTest, Close) {
|
||||
}
|
||||
|
||||
TEST(FileTest, CloseError) {
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
FMT_POSIX(close(f.descriptor()));
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
|
||||
EXPECT_EQ(-1, f.descriptor());
|
||||
}
|
||||
|
||||
TEST(FileTest, Read) {
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
EXPECT_READ(f, FILE_CONTENT);
|
||||
}
|
||||
|
||||
TEST(FileTest, ReadError) {
|
||||
File f("test-file", File::WRONLY);
|
||||
file f("test-file", file::WRONLY);
|
||||
char buf;
|
||||
// We intentionally read from a file opened in the write-only mode to
|
||||
// cause error.
|
||||
@ -305,23 +286,23 @@ TEST(FileTest, ReadError) {
|
||||
}
|
||||
|
||||
TEST(FileTest, Write) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
write(write_end, "test");
|
||||
write_end.close();
|
||||
EXPECT_READ(read_end, "test");
|
||||
}
|
||||
|
||||
TEST(FileTest, WriteError) {
|
||||
File f("test-file", File::RDONLY);
|
||||
file f("test-file", file::RDONLY);
|
||||
// We intentionally write to a file opened in the read-only mode to
|
||||
// cause error.
|
||||
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
|
||||
}
|
||||
|
||||
TEST(FileTest, Dup) {
|
||||
File f = open_file();
|
||||
File copy = File::dup(f.descriptor());
|
||||
file f = open_file();
|
||||
file copy = file::dup(f.descriptor());
|
||||
EXPECT_NE(f.descriptor(), copy.descriptor());
|
||||
EXPECT_EQ(FILE_CONTENT, read(copy, std::strlen(FILE_CONTENT)));
|
||||
}
|
||||
@ -329,29 +310,29 @@ TEST(FileTest, Dup) {
|
||||
#ifndef __COVERITY__
|
||||
TEST(FileTest, DupError) {
|
||||
int value = -1;
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(File::dup(value),
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value),
|
||||
EBADF, "cannot duplicate file descriptor -1");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(FileTest, Dup2) {
|
||||
File f = open_file();
|
||||
File copy = open_file();
|
||||
file f = open_file();
|
||||
file copy = open_file();
|
||||
f.dup2(copy.descriptor());
|
||||
EXPECT_NE(f.descriptor(), copy.descriptor());
|
||||
EXPECT_READ(copy, FILE_CONTENT);
|
||||
}
|
||||
|
||||
TEST(FileTest, Dup2Error) {
|
||||
File f = open_file();
|
||||
file f = open_file();
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.dup2(-1), EBADF,
|
||||
fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor()));
|
||||
}
|
||||
|
||||
TEST(FileTest, Dup2NoExcept) {
|
||||
File f = open_file();
|
||||
File copy = open_file();
|
||||
ErrorCode ec;
|
||||
file f = open_file();
|
||||
file copy = open_file();
|
||||
error_code ec;
|
||||
f.dup2(copy.descriptor(), ec);
|
||||
EXPECT_EQ(0, ec.get());
|
||||
EXPECT_NE(f.descriptor(), copy.descriptor());
|
||||
@ -359,15 +340,15 @@ TEST(FileTest, Dup2NoExcept) {
|
||||
}
|
||||
|
||||
TEST(FileTest, Dup2NoExceptError) {
|
||||
File f = open_file();
|
||||
ErrorCode ec;
|
||||
file f = open_file();
|
||||
error_code ec;
|
||||
SUPPRESS_ASSERT(f.dup2(-1, ec));
|
||||
EXPECT_EQ(EBADF, ec.get());
|
||||
}
|
||||
|
||||
TEST(FileTest, Pipe) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
EXPECT_NE(-1, read_end.descriptor());
|
||||
EXPECT_NE(-1, write_end.descriptor());
|
||||
write(write_end, "test");
|
||||
@ -375,14 +356,23 @@ TEST(FileTest, Pipe) {
|
||||
}
|
||||
|
||||
TEST(FileTest, Fdopen) {
|
||||
File read_end, write_end;
|
||||
File::pipe(read_end, write_end);
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
int read_fd = read_end.descriptor();
|
||||
EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get())));
|
||||
}
|
||||
|
||||
TEST(FileTest, FdopenError) {
|
||||
File f;
|
||||
file f;
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(
|
||||
f.fdopen("r"), EBADF, "cannot associate stream with file descriptor");
|
||||
}
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
TEST(LocaleTest, Strtod) {
|
||||
fmt::Locale locale;
|
||||
const char *start = "4.2", *ptr = start;
|
||||
EXPECT_EQ(4.2, locale.strtod(ptr));
|
||||
EXPECT_EQ(start + 3, ptr);
|
||||
}
|
||||
#endif
|
||||
|
@ -1,50 +1,37 @@
|
||||
/*
|
||||
printf 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++ - printf tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
|
||||
#include "format.h"
|
||||
#include "fmt/core.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
using fmt::format;
|
||||
using fmt::FormatError;
|
||||
using fmt::format_error;
|
||||
|
||||
const unsigned BIG_NUM = INT_MAX + 1u;
|
||||
|
||||
// Makes format string argument positional.
|
||||
std::string make_positional(fmt::StringRef format) {
|
||||
std::string s(format.to_string());
|
||||
static std::string make_positional(fmt::string_view format) {
|
||||
std::string s(format.data(), format.size());
|
||||
s.replace(s.find('%'), 1, "%1$");
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::wstring make_positional(fmt::wstring_view format) {
|
||||
std::wstring s(format.data(), format.size());
|
||||
s.replace(s.find(L'%'), 1, L"%1$");
|
||||
return s;
|
||||
}
|
||||
|
||||
#define EXPECT_PRINTF(expected_output, format, arg) \
|
||||
EXPECT_EQ(expected_output, fmt::sprintf(format, arg)) \
|
||||
<< "format: " << format; \
|
||||
@ -52,6 +39,7 @@ std::string make_positional(fmt::StringRef format) {
|
||||
|
||||
TEST(PrintfTest, NoArgs) {
|
||||
EXPECT_EQ("test", fmt::sprintf("test"));
|
||||
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Escape) {
|
||||
@ -60,6 +48,11 @@ TEST(PrintfTest, Escape) {
|
||||
EXPECT_EQ("% after", fmt::sprintf("%% after"));
|
||||
EXPECT_EQ("before % after", fmt::sprintf("before %% after"));
|
||||
EXPECT_EQ("%s", fmt::sprintf("%%s"));
|
||||
EXPECT_EQ(L"%", fmt::sprintf(L"%%"));
|
||||
EXPECT_EQ(L"before %", fmt::sprintf(L"before %%"));
|
||||
EXPECT_EQ(L"% after", fmt::sprintf(L"%% after"));
|
||||
EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after"));
|
||||
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, PositionalArgs) {
|
||||
@ -79,45 +72,45 @@ TEST(PrintfTest, AutomaticArgIndexing) {
|
||||
|
||||
TEST(PrintfTest, NumberIsTooBigInArgIndex) {
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%{}$", BIG_NUM)),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%{}$d", BIG_NUM)),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
}
|
||||
|
||||
TEST(PrintfTest, SwitchArgIndexing) {
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%1$d%", 1, 2),
|
||||
FormatError, "invalid format string");
|
||||
format_error, "cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%1$d%{}d", BIG_NUM), 1, 2),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%1$d%d", 1, 2),
|
||||
FormatError, "cannot switch from manual to automatic argument indexing");
|
||||
format_error, "cannot switch from manual to automatic argument indexing");
|
||||
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%d%1$", 1, 2),
|
||||
FormatError, "invalid format string");
|
||||
format_error, "cannot switch from automatic to manual argument indexing");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%d%{}$d", BIG_NUM), 1, 2),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%d%1$d", 1, 2),
|
||||
FormatError, "cannot switch from automatic to manual argument indexing");
|
||||
format_error, "cannot switch from automatic to manual argument indexing");
|
||||
|
||||
// Indexing errors override width errors.
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%d%1${}d", BIG_NUM), 1, 2),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%1$d%{}d", BIG_NUM), 1, 2),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
}
|
||||
|
||||
TEST(PrintfTest, InvalidArgIndex) {
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%0$d", 42), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%0$d", 42), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%2$d", 42), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%2$d", 42), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%{}$d", INT_MAX), 42),
|
||||
FormatError, "argument index out of range");
|
||||
format_error, "argument index out of range");
|
||||
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%2$", 42),
|
||||
FormatError, "invalid format string");
|
||||
format_error, "argument index out of range");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%{}$d", BIG_NUM), 42),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
}
|
||||
|
||||
TEST(PrintfTest, DefaultAlignRight) {
|
||||
@ -136,7 +129,7 @@ TEST(PrintfTest, ZeroFlag) {
|
||||
EXPECT_PRINTF("+00042", "%00+6d", 42);
|
||||
|
||||
// '0' flag is ignored for non-numeric types.
|
||||
EXPECT_PRINTF(" x", "%05c", 'x');
|
||||
EXPECT_PRINTF("0000x", "%05c", 'x');
|
||||
}
|
||||
|
||||
TEST(PrintfTest, PlusFlag) {
|
||||
@ -203,23 +196,23 @@ TEST(PrintfTest, Width) {
|
||||
EXPECT_PRINTF(" abc", "%5s", "abc");
|
||||
|
||||
// Width cannot be specified twice.
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%5-5d", 42), FormatError,
|
||||
"unknown format code '-' for integer");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%5-5d", 42), format_error,
|
||||
"invalid type specifier");
|
||||
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%{}d", BIG_NUM), 42),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG(fmt::sprintf(format("%1${}d", BIG_NUM), 42),
|
||||
FormatError, "number is too big");
|
||||
format_error, "number is too big");
|
||||
}
|
||||
|
||||
TEST(PrintfTest, DynamicWidth) {
|
||||
EXPECT_EQ(" 42", fmt::sprintf("%*d", 5, 42));
|
||||
EXPECT_EQ("42 ", fmt::sprintf("%*d", -5, 42));
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%*d", 5.0, 42), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%*d", 5.0, 42), format_error,
|
||||
"width is not integer");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%*d"), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%*d"), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%*d", BIG_NUM, 42), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%*d", BIG_NUM, 42), format_error,
|
||||
"number is too big");
|
||||
}
|
||||
|
||||
@ -262,54 +255,56 @@ TEST(PrintfTest, IgnorePrecisionForNonNumericArg) {
|
||||
TEST(PrintfTest, DynamicPrecision) {
|
||||
EXPECT_EQ("00042", fmt::sprintf("%.*d", 5, 42));
|
||||
EXPECT_EQ("42", fmt::sprintf("%.*d", -5, 42));
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d", 5.0, 42), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d", 5.0, 42), format_error,
|
||||
"precision is not integer");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d"), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d"), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d", BIG_NUM, 42), FormatError,
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d", BIG_NUM, 42), format_error,
|
||||
"number is too big");
|
||||
if (sizeof(fmt::LongLong) != sizeof(int)) {
|
||||
fmt::LongLong prec = static_cast<fmt::LongLong>(INT_MIN) - 1;
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d", prec, 42), FormatError,
|
||||
if (sizeof(long long) != sizeof(int)) {
|
||||
long long prec = static_cast<long long>(INT_MIN) - 1;
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%.*d", prec, 42), format_error,
|
||||
"number is too big");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct MakeSigned { typedef T Type; };
|
||||
struct make_signed { typedef T type; };
|
||||
|
||||
#define SPECIALIZE_MAKE_SIGNED(T, S) \
|
||||
template <> \
|
||||
struct MakeSigned<T> { typedef S Type; }
|
||||
struct make_signed<T> { typedef S type; }
|
||||
|
||||
SPECIALIZE_MAKE_SIGNED(char, signed char);
|
||||
SPECIALIZE_MAKE_SIGNED(unsigned char, signed char);
|
||||
SPECIALIZE_MAKE_SIGNED(unsigned short, short);
|
||||
SPECIALIZE_MAKE_SIGNED(unsigned, int);
|
||||
SPECIALIZE_MAKE_SIGNED(unsigned long, long);
|
||||
SPECIALIZE_MAKE_SIGNED(fmt::ULongLong, fmt::LongLong);
|
||||
SPECIALIZE_MAKE_SIGNED(unsigned long long, long long);
|
||||
|
||||
// Test length format specifier ``length_spec``.
|
||||
template <typename T, typename U>
|
||||
void TestLength(const char *length_spec, U value) {
|
||||
fmt::LongLong signed_value = value;
|
||||
fmt::ULongLong unsigned_value = value;
|
||||
long long signed_value = 0;
|
||||
unsigned long long unsigned_value = 0;
|
||||
// Apply integer promotion to the argument.
|
||||
fmt::ULongLong max = std::numeric_limits<U>::max();
|
||||
using fmt::internal::check;
|
||||
if (check(max <= static_cast<unsigned>(std::numeric_limits<int>::max()))) {
|
||||
using std::numeric_limits;
|
||||
unsigned long long max = numeric_limits<U>::max();
|
||||
using fmt::internal::const_check;
|
||||
if (const_check(max <= static_cast<unsigned>(numeric_limits<int>::max()))) {
|
||||
signed_value = static_cast<int>(value);
|
||||
unsigned_value = static_cast<int>(value);
|
||||
} else if (check(max <= std::numeric_limits<unsigned>::max())) {
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
} else if (const_check(max <= numeric_limits<unsigned>::max())) {
|
||||
signed_value = static_cast<unsigned>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
}
|
||||
using fmt::internal::MakeUnsigned;
|
||||
if (sizeof(U) <= sizeof(int) && sizeof(int) < sizeof(T)) {
|
||||
signed_value = unsigned_value =
|
||||
static_cast<typename MakeUnsigned<unsigned>::Type>(value);
|
||||
signed_value = static_cast<long long>(value);
|
||||
unsigned_value =
|
||||
static_cast<typename std::make_unsigned<unsigned>::type>(value);
|
||||
} else {
|
||||
signed_value = static_cast<typename MakeSigned<T>::Type>(value);
|
||||
unsigned_value = static_cast<typename MakeUnsigned<T>::Type>(value);
|
||||
signed_value = static_cast<typename make_signed<T>::type>(value);
|
||||
unsigned_value = static_cast<typename std::make_unsigned<T>::type>(value);
|
||||
}
|
||||
std::ostringstream os;
|
||||
os << signed_value;
|
||||
@ -336,23 +331,20 @@ void TestLength(const char *length_spec) {
|
||||
TestLength<T>(length_spec, -42);
|
||||
TestLength<T>(length_spec, min);
|
||||
TestLength<T>(length_spec, max);
|
||||
using fmt::internal::check;
|
||||
fmt::LongLong long_long_min = std::numeric_limits<fmt::LongLong>::min();
|
||||
if (check(min >= 0) || check(static_cast<fmt::LongLong>(min) > long_long_min))
|
||||
TestLength<T>(length_spec, fmt::LongLong(min) - 1);
|
||||
fmt::ULongLong long_long_max = std::numeric_limits<fmt::LongLong>::max();
|
||||
if (check(max < 0 || static_cast<fmt::ULongLong>(max) < long_long_max))
|
||||
TestLength<T>(length_spec, fmt::LongLong(max) + 1);
|
||||
TestLength<T>(length_spec, static_cast<long long>(min) - 1);
|
||||
unsigned long long long_long_max = std::numeric_limits<long long>::max();
|
||||
if (static_cast<unsigned long long>(max) < long_long_max)
|
||||
TestLength<T>(length_spec, static_cast<long long>(max) + 1);
|
||||
TestLength<T>(length_spec, std::numeric_limits<short>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned short>::max());
|
||||
TestLength<T>(length_spec, std::numeric_limits<int>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<int>::max());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::max());
|
||||
TestLength<T>(length_spec, std::numeric_limits<fmt::LongLong>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<fmt::LongLong>::max());
|
||||
TestLength<T>(length_spec, std::numeric_limits<fmt::ULongLong>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<fmt::ULongLong>::max());
|
||||
TestLength<T>(length_spec, std::numeric_limits<long long>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<long long>::max());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::max());
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Length) {
|
||||
@ -363,8 +355,8 @@ TEST(PrintfTest, Length) {
|
||||
TestLength<unsigned short>("h");
|
||||
TestLength<long>("l");
|
||||
TestLength<unsigned long>("l");
|
||||
TestLength<fmt::LongLong>("ll");
|
||||
TestLength<fmt::ULongLong>("ll");
|
||||
TestLength<long long>("ll");
|
||||
TestLength<unsigned long long>("ll");
|
||||
TestLength<intmax_t>("j");
|
||||
TestLength<std::size_t>("z");
|
||||
TestLength<std::ptrdiff_t>("t");
|
||||
@ -381,15 +373,25 @@ TEST(PrintfTest, Bool) {
|
||||
TEST(PrintfTest, Int) {
|
||||
EXPECT_PRINTF("-42", "%d", -42);
|
||||
EXPECT_PRINTF("-42", "%i", -42);
|
||||
unsigned u = -42;
|
||||
unsigned u = 0 - 42u;
|
||||
EXPECT_PRINTF(fmt::format("{}", u), "%u", -42);
|
||||
EXPECT_PRINTF(fmt::format("{:o}", u), "%o", -42);
|
||||
EXPECT_PRINTF(fmt::format("{:x}", u), "%x", -42);
|
||||
EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, long_long) {
|
||||
// fmt::printf allows passing long long arguments to %d without length
|
||||
// specifiers.
|
||||
long long max = std::numeric_limits<long long>::max();
|
||||
EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Float) {
|
||||
EXPECT_PRINTF("392.650000", "%f", 392.65);
|
||||
EXPECT_PRINTF("392.65", "%.2f", 392.65);
|
||||
EXPECT_PRINTF("392.6", "%.1f", 392.65);
|
||||
EXPECT_PRINTF("393", "%.f", 392.65);
|
||||
EXPECT_PRINTF("392.650000", "%F", 392.65);
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%e", 392.65);
|
||||
@ -408,7 +410,7 @@ TEST(PrintfTest, Inf) {
|
||||
double inf = std::numeric_limits<double>::infinity();
|
||||
for (const char* type = "fega"; *type; ++type) {
|
||||
EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf);
|
||||
char upper = std::toupper(*type);
|
||||
char upper = static_cast<char>(std::toupper(*type));
|
||||
EXPECT_PRINTF("INF", fmt::format("%{}", upper), inf);
|
||||
}
|
||||
}
|
||||
@ -418,32 +420,42 @@ TEST(PrintfTest, Char) {
|
||||
int max = std::numeric_limits<int>::max();
|
||||
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
||||
//EXPECT_PRINTF("x", "%lc", L'x');
|
||||
// TODO: test wchar_t
|
||||
EXPECT_PRINTF(L"x", L"%c", L'x');
|
||||
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, String) {
|
||||
EXPECT_PRINTF("abc", "%s", "abc");
|
||||
const char *null_str = 0;
|
||||
const char *null_str = nullptr;
|
||||
EXPECT_PRINTF("(null)", "%s", null_str);
|
||||
EXPECT_PRINTF(" (null)", "%10s", null_str);
|
||||
// TODO: wide string
|
||||
EXPECT_PRINTF(L"abc", L"%s", L"abc");
|
||||
const wchar_t *null_wstr = nullptr;
|
||||
EXPECT_PRINTF(L"(null)", L"%s", null_wstr);
|
||||
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Pointer) {
|
||||
int n;
|
||||
void *p = &n;
|
||||
EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
|
||||
p = 0;
|
||||
p = nullptr;
|
||||
EXPECT_PRINTF("(nil)", "%p", p);
|
||||
EXPECT_PRINTF(" (nil)", "%10p", p);
|
||||
const char *s = "test";
|
||||
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
|
||||
const char *null_str = 0;
|
||||
const char *null_str = nullptr;
|
||||
EXPECT_PRINTF("(nil)", "%p", null_str);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Custom) {
|
||||
EXPECT_PRINTF("abc", "%s", TestString("abc"));
|
||||
p = &n;
|
||||
EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p);
|
||||
p = nullptr;
|
||||
EXPECT_PRINTF(L"(nil)", L"%p", p);
|
||||
EXPECT_PRINTF(L" (nil)", L"%10p", p);
|
||||
const wchar_t *w = L"test";
|
||||
EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w);
|
||||
const wchar_t *null_wstr = nullptr;
|
||||
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Location) {
|
||||
@ -466,13 +478,24 @@ TEST(PrintfTest, Examples) {
|
||||
}
|
||||
|
||||
TEST(PrintfTest, PrintfError) {
|
||||
fmt::File read_end, write_end;
|
||||
fmt::File::pipe(read_end, write_end);
|
||||
fmt::file read_end, write_end;
|
||||
fmt::file::pipe(read_end, write_end);
|
||||
int result = fmt::fprintf(read_end.fdopen("r").get(), "test");
|
||||
EXPECT_LT(result, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(PrintfTest, WideString) {
|
||||
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", TestWString(L"abc")));
|
||||
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, PrintfCustom) {
|
||||
EXPECT_EQ("abc", fmt::sprintf("%s", TestString("abc")));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, OStream) {
|
||||
std::ostringstream os;
|
||||
int ret = fmt::fprintf(os, "Don't %s!", "panic");
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
EXPECT_EQ(12, ret);
|
||||
}
|
||||
|
88
test/ranges-test.cc
Normal file
88
test/ranges-test.cc
Normal file
@ -0,0 +1,88 @@
|
||||
// Formatting library for C++ - the core API
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "fmt/ranges.h"
|
||||
|
||||
/// Check if 'if constexpr' is supported.
|
||||
#if (__cplusplus > 201402L) || \
|
||||
(defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
|
||||
|
||||
#include "gtest.h"
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
TEST(RangesTest, FormatVector) {
|
||||
std::vector<int32_t> iv{1, 2, 3, 5, 7, 11};
|
||||
auto ivf = fmt::format("{}", iv);
|
||||
EXPECT_EQ("{1, 2, 3, 5, 7, 11}", ivf);
|
||||
}
|
||||
|
||||
TEST(RangesTest, FormatVector2) {
|
||||
std::vector<std::vector<int32_t>> ivv{{1, 2}, {3, 5}, {7, 11}};
|
||||
auto ivf = fmt::format("{}", ivv);
|
||||
EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", ivf);
|
||||
}
|
||||
|
||||
TEST(RangesTest, FormatMap) {
|
||||
std::map<std::string, int32_t> simap{{"one", 1}, {"two", 2}};
|
||||
EXPECT_EQ("{(\"one\", 1), (\"two\", 2)}", fmt::format("{}", simap));
|
||||
}
|
||||
|
||||
TEST(RangesTest, FormatPair) {
|
||||
std::pair<int64_t, float> pa1{42, 3.14159265358979f};
|
||||
EXPECT_EQ("(42, 3.14159)", fmt::format("{}", pa1));
|
||||
}
|
||||
|
||||
TEST(RangesTest, FormatTuple) {
|
||||
std::tuple<int64_t, float, std::string, char> tu1{42, 3.14159265358979f,
|
||||
"this is tuple", 'i'};
|
||||
EXPECT_EQ("(42, 3.14159, \"this is tuple\", 'i')", fmt::format("{}", tu1));
|
||||
}
|
||||
|
||||
struct my_struct {
|
||||
int32_t i;
|
||||
std::string str; // can throw
|
||||
template <std::size_t N>
|
||||
decltype(auto) get() const noexcept {
|
||||
if constexpr (N == 0)
|
||||
return i;
|
||||
else if constexpr (N == 1)
|
||||
return fmt::string_view{str};
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
decltype(auto) get(const my_struct& s) noexcept {
|
||||
return s.get<N>();
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct tuple_size<my_struct> : std::integral_constant<std::size_t, 2> {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct tuple_element<N, my_struct> {
|
||||
using type = decltype(std::declval<my_struct>().get<N>());
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
TEST(RangesTest, FormatStruct) {
|
||||
my_struct mst{13, "my struct"};
|
||||
EXPECT_EQ("(13, \"my struct\")", fmt::format("{}", mst));
|
||||
}
|
||||
|
||||
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
|
||||
// 201402L && _MSC_VER >= 1910)
|
@ -1,29 +1,9 @@
|
||||
/*
|
||||
Test version of FMT_ASSERT
|
||||
|
||||
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.
|
||||
*/
|
||||
// Formatting library for C++ - test version of FMT_ASSERT
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_TEST_ASSERT_H
|
||||
#define FMT_TEST_ASSERT_H
|
||||
|
@ -1,32 +1,12 @@
|
||||
/*
|
||||
Tests of additional gtest macros.
|
||||
|
||||
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++ - test main function.
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <gtest/gtest.h>
|
||||
#include "gtest.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
|
59
test/time-test.cc
Normal file
59
test/time-test.cc
Normal file
@ -0,0 +1,59 @@
|
||||
// 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 "gmock.h"
|
||||
#include "fmt/time.h"
|
||||
|
||||
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, 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)));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
40
test/util.cc
40
test/util.cc
@ -1,29 +1,9 @@
|
||||
/*
|
||||
Test utilities.
|
||||
|
||||
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++ - test utilities
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "util.h"
|
||||
#include <cstring>
|
||||
@ -52,12 +32,12 @@ std::string get_system_error(int error_code) {
|
||||
|
||||
const char *const FILE_CONTENT = "Don't panic!";
|
||||
|
||||
fmt::BufferedFile open_buffered_file(FILE **fp) {
|
||||
fmt::File read_end, write_end;
|
||||
fmt::File::pipe(read_end, write_end);
|
||||
fmt::buffered_file open_buffered_file(FILE **fp) {
|
||||
fmt::file read_end, write_end;
|
||||
fmt::file::pipe(read_end, write_end);
|
||||
write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT));
|
||||
write_end.close();
|
||||
fmt::BufferedFile f = read_end.fdopen("r");
|
||||
fmt::buffered_file f = read_end.fdopen("r");
|
||||
if (fp)
|
||||
*fp = f.get();
|
||||
return f;
|
||||
|
59
test/util.h
59
test/util.h
@ -1,35 +1,15 @@
|
||||
/*
|
||||
Test utilities.
|
||||
|
||||
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++ - test utilities
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "posix.h"
|
||||
#include "fmt/posix.h"
|
||||
|
||||
enum {BUFFER_SIZE = 256};
|
||||
|
||||
@ -55,7 +35,7 @@ std::string get_system_error(int error_code);
|
||||
extern const char *const FILE_CONTENT;
|
||||
|
||||
// Opens a buffered file for reading.
|
||||
fmt::BufferedFile open_buffered_file(FILE **fp = 0);
|
||||
fmt::buffered_file open_buffered_file(FILE **fp = nullptr);
|
||||
|
||||
inline FILE *safe_fopen(const char *filename, const char *mode) {
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
@ -78,11 +58,7 @@ class BasicTestString {
|
||||
public:
|
||||
explicit BasicTestString(const Char *value = EMPTY) : value_(value) {}
|
||||
|
||||
friend std::basic_ostream<Char> &operator<<(
|
||||
std::basic_ostream<Char> &os, const BasicTestString &s) {
|
||||
os << s.value_;
|
||||
return os;
|
||||
}
|
||||
const std::basic_string<Char> &value() const { return value_; }
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
@ -90,3 +66,20 @@ const Char BasicTestString<Char>::EMPTY[] = {0};
|
||||
|
||||
typedef BasicTestString<char> TestString;
|
||||
typedef BasicTestString<wchar_t> TestWString;
|
||||
|
||||
template <typename Char>
|
||||
std::basic_ostream<Char> &operator<<(
|
||||
std::basic_ostream<Char> &os, const BasicTestString<Char> &s) {
|
||||
os << s.value();
|
||||
return os;
|
||||
}
|
||||
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
|
||||
|
||||
int year() const { return year_; }
|
||||
int month() const { return month_; }
|
||||
int day() const { return day_; }
|
||||
};
|
||||
|
Reference in New Issue
Block a user