Compare commits

..

280 Commits

Author SHA1 Message Date
Victor Zverovich
a0b8a92e3d Update version 2023-05-09 15:55:39 -07:00
Victor Zverovich
5cf2342aa2 Bump version 2023-05-09 15:37:01 -07:00
Victor Zverovich
fe9d39d7cb Update changelog 2023-05-09 14:27:48 -07:00
Victor Zverovich
4c98561979 Update changelog 2023-05-09 14:25:56 -07:00
Victor Zverovich
403b271ed7 Update changelog 2023-05-09 14:20:59 -07:00
Victor Zverovich
2c991e1af6 Update changelog 2023-05-08 14:43:16 -07:00
Victor Zverovich
c984df9815 Remove an unused function from internal class 2023-05-08 13:35:23 -07:00
Victor Zverovich
fbf21ed224 Update changelog 2023-05-08 13:34:29 -07:00
Victor Zverovich
575583144e Update changelog 2023-05-08 11:37:33 -07:00
Victor Zverovich
e7f6888c7a Update changelog 2023-05-08 09:49:39 -07:00
Victor Zverovich
39db2dfd06 Update changelog 2023-05-08 09:41:57 -07:00
Victor Zverovich
9b7829e264 Update changelog 2023-05-08 09:15:07 -07:00
Victor Zverovich
1e0ce567ef Fix formatting of paths containing invalid Unicode 2023-05-07 10:05:15 -07:00
Vladislav Shchapov
dde8cf3bb7 Unification utf16/utf32 to utf8 conversion
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-05-06 08:32:51 -07:00
Vladislav Shchapov
e84b00e014 Workaround to error: variable 'n' set but not used [-Werror,-Wunused-but-set-variable]
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-05-06 08:32:51 -07:00
Victor Zverovich
b12ffea4fb Add filesystem_error test back 2023-05-06 07:49:46 -07:00
Victor Zverovich
f61f15cc5b Suppress a false positive in gcc 2023-05-06 06:54:30 -07:00
Daniela Engert
192df93d7b modules missing pieces (#3399)
- don't export names from `detail`
- put more headers into the global module fragment
- support MSYS2 and Clang
2023-05-05 10:21:18 -07:00
mogemimi
d8973bf16b Add FMT_STRING for format_to() call (#3413) 2023-05-03 07:58:40 -07:00
Daniela Engert
d7a8e50cb5 Improve module testing (#3397)
* use the standard `test-main.cc` component instead of injected test infrastructure sources
 * undo now obsolete commit `00235d8a` from July 2021
 * Clang cannot import user-defined literals as it seems -> disable test
 * Clang emits duplicate, non-mergeable copies of `detail::buffer`'s vtable, causing linker errors -> disable test
2023-05-03 07:56:24 -07:00
Victor Zverovich
02cae7e48a Improve handling of Unicode in paths 2023-04-30 09:58:59 -07:00
Vertexwahn
53162142b2 Remove .bazelrc mention from Bazel related readme (#3411) 2023-04-29 16:25:00 -07:00
Vertexwahn
5bcf0d7f97 Bazel support (#3406)
* Bazel support: Remove not needed .bazelrc file

* Bump tested Bazel version

* Apply buildifier to format Bazel build files

* Add note about Bzlmod
2023-04-29 07:45:49 -07:00
Vertexwahn
f8c9fabd94 Fix spelling (#3404) 2023-04-25 11:47:37 -07:00
Jonathan Müller
62ff4e1dbd Remove foonathan from maintainer list (#3402) 2023-04-23 13:35:05 -07:00
Daniela Engert
f449ca0525 Name vfprintf clashes with the identically named declaration in 'stdio.h' if that happens to be #included into the same TU. Fix this by using qualified name lookup instead of unqualified lookup that also enables ADL. (#3400) 2023-04-23 07:10:57 -07:00
Daniela Engert
eafcd3c8e1 Optionally attach declarations to the global module rather than module fmt (#3387)
This allows coexistence with TUs that use {fmt} through #include without duplicating declarations, definitions, linker symbols, and object code.
2023-04-23 06:04:36 -07:00
Victor Zverovich
18154cc903 Simplify print 2023-04-22 14:03:40 -07:00
Victor Zverovich
0de789cf29 Update changelog 2023-04-22 09:20:28 -07:00
Daniela Engert
c039389223 export names only once (#3392)
names declared to be exported at the point of introduction into a namespace *must not* be (re-)declared as exported later in the TU, e.g. when they are redeclared, defined, or specialized. [module.export]/6

Drive-by fix found during module testing: add a missing `detail::` name qualification
2023-04-22 06:21:06 -07:00
Louis Wilson
93e81bb5d8 Fix C4365 (signed/unsigned mismatch) warning on 32-bit Windows (#3398) 2023-04-20 17:36:05 -07:00
Kevin Hwang
e7d6eb6794 Update tests to use recommended MOCK_METHOD (#3395) 2023-04-20 13:16:21 -07:00
Daniela Engert
18e7a2532b Remove obsolete msvc workarounds (#3388)
This bug in the modules implementation is fixed since at least msvc 19.34, possibly even earlier like 19.32.
2023-04-19 17:09:51 -07:00
Daniela Engert
0489c19dcb fix and improve module (#3386)
* export public documented API
* don't export `namespace detail`
* add `std.h` into module
* add missing namespace qualification in `xchar.h`
* fix call to `detail::get_iterator` in `xchar.h`
* fix ambiguous overload of `detail::isfinite` in `chrono.h`
2023-04-18 06:47:01 -07:00
Victor Zverovich
8ec94ac6a5 Use full path to pcm 2023-04-14 11:57:17 -07:00
Victor Zverovich
d97d8cea67 Push module check to test 2023-04-14 11:42:01 -07:00
Victor Zverovich
d8a2698e6c Fix compilation as a C++20 module with gcc 13 2023-04-14 09:49:07 -07:00
Victor Zverovich
d9c19940a3 Update add_module_library 2023-04-13 17:13:52 -07:00
Victor Zverovich
4b5ae0b0ef Remove unnecessary module support check 2023-04-13 16:53:45 -07:00
Victor Zverovich
75f3b1c094 Use add_module_library 2023-04-12 09:10:34 -07:00
Victor Zverovich
faf83406a9 Workaround cmake issue 2023-04-11 15:59:08 -07:00
Victor Zverovich
165814d57a Add module support to CMake 2023-04-11 14:38:34 -07:00
June Liu
33f7150778 Fix error C2668 on msvc (#3378) 2023-04-11 06:27:28 -07:00
Victor Zverovich
c98e5a08a4 Fix modular build on clang 2023-04-10 12:07:25 -07:00
Victor Zverovich
119c6bd16f Move the modules check 2023-04-10 09:28:00 -07:00
Victor Zverovich
77eeb71830 Remove unused headers 2023-04-10 09:08:19 -07:00
Victor Zverovich
13bf99f9db Enable modules in clang 16 2023-04-10 08:44:43 -07:00
Victor Zverovich
1d0257e4c0 FMT_MODULE_EXPORT_* -> FMT_EXPORT_* 2023-04-10 08:33:39 -07:00
Victor Zverovich
4613d48fd3 FMT_EXPORT -> FMT_LIB_EXPORT 2023-04-10 08:24:23 -07:00
Victor Zverovich
4a4a2a2bd6 Fix diagnostics 2023-04-09 09:30:20 -07:00
Victor Zverovich
fce74caa15 Disable problematic implicit conversions 2023-04-09 09:08:46 -07:00
Victor Zverovich
02bf4d1c1c Disable to_string_view ADL 2023-04-09 08:49:05 -07:00
Victor Zverovich
466e0650ec Remove problematic workaround 2023-04-09 08:04:11 -07:00
Victor Zverovich
029caa8ea2 Update changelog 2023-04-09 07:01:06 -07:00
Victor Zverovich
e406ddbfaf Remove broken part of the config 2023-04-08 08:46:43 -07:00
Victor Zverovich
9095679536 Update changelog 2023-04-08 08:39:17 -07:00
Björn Schäpers
7f46cb75b8 ranges: Fix extra semi (#3374) 2023-04-06 08:31:07 -07:00
Victor Zverovich
4e3f381058 Update changelog 2023-04-02 07:13:42 -07:00
Mikhail Paulyshka
d3c10f5167 fix compilation for MSDOS (#3369) 2023-04-01 13:40:53 -07:00
Victor Zverovich
ab956f600f Update changelog 2023-04-01 08:04:47 -07:00
Vladislav Shchapov
97aedeab48 Workaround a double-double hexfloat format (#3366)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-04-01 07:19:23 -07:00
Victor Zverovich
bce8d4ed08 Remove stray comment 2023-03-27 11:04:00 -07:00
Victor Zverovich
a91c7b286d Cleanup the core API 2023-03-26 21:07:26 -07:00
Victor Zverovich
19c074e477 Remove deprecated fallback formatter 2023-03-26 08:45:06 -07:00
Victor Zverovich
41cfc739fe Generalize format_as 2023-03-26 07:37:51 -07:00
Vladislav Shchapov
f6276a2c2b Force use a signed char (On ARM char is unsigned by default) (#3362)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-03-22 11:34:59 -07:00
Victor Zverovich
6002ddf825 Remove a deprecated option 2023-03-19 12:17:43 -07:00
Victor Zverovich
6549ffde8e Improve format_as safety 2023-03-19 12:09:54 -07:00
Alecto Irene Perez
d9bc5f1320 Fix code causing spurious Wstringop-overflow warning
See #2989, #3054, and others
2023-03-18 09:29:54 -07:00
Victor Zverovich
9c5cd998d1 Remove unused functions 2023-03-18 08:40:16 -07:00
Barry Revzin
93bfa05382 %T is %H:%M:%S (#3349) 2023-03-18 07:16:22 -07:00
TheOmegaCarrot
d8e1c4265a fix case of variant which is valueless by exception (#3347)
Co-authored-by: theomegacarrot <theomegacarrot@gmail.com>
2023-03-18 07:07:06 -07:00
tmartin-gh
e1720c0e51 Fix CUDA nvcc warning fmt/include/fmt/core.h(295): warning #1675-D: unrecognized GCC pragma (#3352) 2023-03-17 11:35:35 -07:00
Gleb Mazovetskiy
7f882918eb write_floating_seconds: Fall back to ::round (#3343)
On some toolchains, `std::round` is not available.

Fixes #3342
2023-03-12 09:34:19 -07:00
Shawn Zhong
cbc7b8d5c1 Cleanup dead variable (#3338) 2023-03-10 09:17:43 -08:00
Vladislav Shchapov
050293646f Path is not escaped twice in the debug mode (#3321)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-03-05 08:01:06 -08:00
Cloyce D. Spradling
3daf33837c Enable consteval for Xcode 14.0.1 and later (#3331)
Co-authored-by: Cloyce D. Spradling <cloyce_spradling@apple.com>
2023-03-04 09:22:43 -08:00
Victor Zverovich
e0748e61dd Fix recursion check in range formatting 2023-03-04 08:20:32 -08:00
Joyce
b94e1016fa chore: set permission to cifuzz.yml (#3328)
Signed-off-by: Joyce <joycebrum@google.com>
2023-03-01 07:58:59 -08:00
Victor Zverovich
98699719f8 Make # handling consistent with std::format 2023-02-26 10:15:16 -08:00
Victor Zverovich
48dfbcaa95 Improve license and PR template wording 2023-02-26 09:08:53 -08:00
Antony Polukhin
c644c753d7 Add '🐙 userver framework' to the projects 2023-02-26 08:42:55 -08:00
Vladislav Shchapov
73b7cee7fb Fix for issue #3325 (#3326)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-02-26 06:59:41 -08:00
tom-huntington
5b8302079d Add optional support (#3303) 2023-02-25 06:45:56 -08:00
luzpaz
3a69529e8b Fix various typos (#3312)
Found via `codespell -q 3 -S ./test/gtest -L "fo,pres,seh,wronly"`
2023-02-21 15:14:41 -08:00
Victor Zverovich
76f520835f Call element parse in tuple parse 2023-02-20 12:54:20 -08:00
Victor Zverovich
507c3042d8 class -> typename 2023-02-18 10:23:42 -08:00
Victor Zverovich
1741e90dec Always call parse in range formatter 2023-02-18 09:58:37 -08:00
Victor Zverovich
d646fd0daf Minor cleanup 2023-02-16 11:21:08 -08:00
Froster
b5c2f74f45 change sopen_s to wsopen_s (fmtlib#3234) (#3293) 2023-02-16 11:17:55 -08:00
Vladislav Shchapov
e03753c4ac Add ubuntu mirrors (#3302)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-02-12 07:46:50 -08:00
Russell Greene
6e6eb63770 [msvc] fix warning about non-inline variable 2023-02-11 09:45:13 -08:00
Shawn Zhong
3c5464ba1c Fix OpenBSD build error (#3295) 2023-02-11 08:46:28 -08:00
Victor Zverovich
655046d24f Fix container adaptor formatting 2023-02-10 09:45:37 -08:00
Shawn Zhong
581c6292c9 Add formatters for container adapters (#3279) 2023-02-08 17:25:41 -08:00
Shawn Zhong
7718eeeacc Implement glibc ext for sec, min, and hour (#3271) 2023-02-08 09:22:58 -08:00
Victor Zverovich
44e0eea94e Use FMT_HAS_INCLUDE and apply clang-format 2023-02-08 07:19:10 -08:00
Roman-Koshelev
99070899b7 Fix errors setting of FMT_USE_FLOAT128 (#3259) 2023-02-08 07:15:02 -08:00
Barry Revzin
05e3a9233a Allowing formatting non-copyable ranges. (#3290) 2023-01-27 12:29:57 -08:00
Ivan Shynkarenka
70db193f09 Visual Studio 2022: fmt/format-inl.h(1145,60): warning C4310: cast truncates constant value #3287 (#3288) 2023-01-26 11:46:30 -08:00
Victor Zverovich
a2c05a10ec Workaround a bug in MSVC <= 19.22 2023-01-25 10:34:28 +13:00
Victor Zverovich
cae9bf45b9 Simplify apidoc comments 2023-01-25 09:32:03 +13:00
Shawn Zhong
87c066a35b Implement println (#3267) 2023-01-24 12:30:00 -08:00
Vladislav Shchapov
9409b2e4d8 Workaround for incompatibility between libstdc++ consteval-based std::is_constant_evaluated() implementation and clang-14 (#3281)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2023-01-22 11:29:34 -08:00
jk-jeon
f89cd276f7 Refactor countl_zero fallback (#3276) 2023-01-17 15:04:34 -08:00
Victor Zverovich
240b728d81 Report an error on overflow 2023-01-18 11:52:33 +13:00
Shawn Zhong
dfbb952b2c Fix empty spec for time point (#3275) 2023-01-16 11:48:00 -08:00
Shawn Zhong
39971eb336 Fix localized format for float-point numbers (#3272) 2023-01-15 11:47:24 -08:00
jk-jeon
0f42c17d85 Implement a new formatting algorithm for small given precision (#3269)
Implement the formatting algorithm for small given precision discussed in https://github.com/fmtlib/fmt/issues/3262 and https://github.com/fmtlib/fmt/pull/2750
2023-01-14 11:30:20 -08:00
Kenny Weiss
bfc0924eac Bugfix for fmt::printf on Power9 architecture with the XL compiler (#3256) 2023-01-13 11:36:00 -08:00
Shawn Zhong
676c2a107e Fix negative subsec for time_point (#3261) 2023-01-11 11:36:50 -08:00
François Carouge
2c80cedc39 Fix standard default installation target presence (#3264) 2023-01-10 17:56:00 -08:00
Shawn Zhong
dda53082be Support fill, align & width for time point (#3260) 2023-01-09 11:25:31 -08:00
Victor Zverovich
2622cd23e6 Simplify arg_mapper 2023-01-03 16:35:21 -08:00
Victor Zverovich
9e4a54fa6e Disable remaining implicit conversions 2023-01-03 12:08:42 -08:00
Victor Zverovich
9ce6480676 Disble deprecated implicit enum conversions 2023-01-03 10:24:31 -08:00
Victor Zverovich
9121f9b1d3 Enable format_as for classes 2023-01-03 08:58:23 -08:00
Victor Zverovich
b7535365b2 Enable format_as for non-integral types 2023-01-02 13:37:13 -08:00
Victor Zverovich
09ed1ddb9c Cleanup tests 2023-01-02 09:58:12 -08:00
Victor Zverovich
0ec65d99aa Merge parse_presentation_type into parse_format_specs 2023-01-02 09:19:32 -08:00
Victor Zverovich
71e4e02722 Cleanup vprintf 2023-01-02 08:37:00 -08:00
Victor Zverovich
aad546baa5 Simplify presentation type parsing 2023-01-01 16:14:17 -08:00
Victor Zverovich
14a69fcc54 Use parse_align 2023-01-01 10:44:55 -08:00
Victor Zverovich
bf34ffd33f Refactor format string parsing 2023-01-01 10:21:47 -08:00
Victor Zverovich
6056e07125 Simplify symbols 2022-12-30 19:35:05 -08:00
Victor Zverovich
aa99b86409 Minor cleanup 2022-12-30 18:51:15 -08:00
Victor Zverovich
6ade2eb4e5 Inline all_int_set 2022-12-30 17:05:08 -08:00
Victor Zverovich
caa6974942 Simplify parse functions 2022-12-30 16:34:36 -08:00
Victor Zverovich
a73a9b6a84 Refactor format string checks 2022-12-30 16:03:41 -08:00
Victor Zverovich
72785a3aba Cleanup write 2022-12-30 13:53:55 -08:00
Victor Zverovich
0c3dd5ddd7 Remove redundant check 2022-12-30 12:55:19 -08:00
Victor Zverovich
739b600f40 Remove iterator shenanigans 2022-12-30 12:37:22 -08:00
Victor Zverovich
3710c4d38f Link to dragonbox 2022-12-30 12:07:55 -08:00
Victor Zverovich
a05ba44df8 Simplify format string parsing 2022-12-30 11:23:42 -08:00
Victor Zverovich
ffb9b1d13c Improve handling of signed types 2022-12-30 10:31:39 -08:00
Victor Zverovich
32190859ec Fix handling of char 2022-12-30 09:11:42 -08:00
Victor Zverovich
8fe4d97d5e Reduce template instantiations 2022-12-30 08:44:23 -08:00
Orvid King
7e5a959564 Fix build with MSVC C++20 modules (#3254)
When using fmt with C++20 modules under MSVC, it can end up requiring certain things to have storage that would not otherwise have needed to. Since I didn't see anything that was already doing detection for `inline constexpr` variable support, I've just moved the entire thing into the only function where it's used.
2022-12-28 19:58:57 -08:00
Victor Zverovich
9e60304869 Clarify that unused args are allowed 2022-12-28 06:58:04 -08:00
Victor Zverovich
7ad48c1f65 Cleanup core.h 2022-12-26 09:14:15 -08:00
Victor Zverovich
a921a596e7 Cleanup core.h 2022-12-26 07:46:31 -08:00
Victor Zverovich
3e762fdf5c Use ignore_unused 2022-12-26 06:57:21 -08:00
Victor Zverovich
79981a2528 Cleanup ranges formatting 2022-12-25 20:05:20 -08:00
Victor Zverovich
bd12aaa98e Simplify format string parsing 2022-12-25 12:31:38 -08:00
Victor Zverovich
b8f36207c9 Simplify format string parsing 2022-12-25 11:47:43 -08:00
Victor Zverovich
d907786f04 Move anchor to where it belongs 2022-12-25 10:58:54 -08:00
Victor Zverovich
f2355bbe5e Fix docs 2022-12-25 10:25:35 -08:00
Victor Zverovich
f398c94761 Fix docs 2022-12-25 10:24:36 -08:00
Vladislav Shchapov
4841784e82 Simplify C99 strftime detection conditions
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-25 10:16:19 -08:00
Vladislav Shchapov
cb72c23e9e Improve timezone tests
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-25 10:16:19 -08:00
Vladislav Shchapov
583f2d8209 Set timezone for chrono tests
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-25 10:16:19 -08:00
Victor Zverovich
32c4390704 Minor cleanup 2022-12-25 08:59:25 -08:00
Victor Zverovich
3a5e19fbf5 Minor cleanup 2022-12-25 08:42:16 -08:00
Victor Zverovich
dfb857ebef Refactor format spec parsing 2022-12-25 07:25:32 -08:00
Victor Zverovich
9ea9b6bcb1 Cleanup arg id parsing 2022-12-24 16:33:57 -08:00
Victor Zverovich
2b0ff62a7f Remove unused template arg from format_string_checker 2022-12-24 15:46:34 -08:00
Victor Zverovich
d1745084e0 Simplify parse context 2022-12-24 15:20:24 -08:00
Victor Zverovich
407e7b7b6d basic_format_specs -> format_specs 2022-12-24 14:34:50 -08:00
Victor Zverovich
3cf9794755 Cleanup format string parsing 2022-12-24 13:47:20 -08:00
Victor Zverovich
934c8e5f76 Refactor precision parsing 2022-12-24 13:29:23 -08:00
Shawn Zhong
fc96938345 Remove empty semicolon 2022-12-24 09:54:55 -08:00
Victor Zverovich
f0ab112c34 Cleanup parsing 2022-12-24 09:40:35 -08:00
Victor Zverovich
9660e5b956 Remove redundant tests 2022-12-24 07:28:13 -08:00
Shawn Zhong
a585571e90 Ignore 0 character with align 2022-12-23 19:36:05 -08:00
Victor Zverovich
840ec8569d Cleanup width handlers 2022-12-23 19:18:35 -08:00
Victor Zverovich
1dadeb8a33 Refactor width parsing 2022-12-23 18:29:59 -08:00
Victor Zverovich
275b4b3417 Remove obsolete parse-benchmark 2022-12-23 12:50:08 -08:00
Vladislav Shchapov
e004f1d699 Fix for issue #3241
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-23 12:26:20 -08:00
Victor Zverovich
bde1a6070d Simplify fill and alignment parsing 2022-12-23 10:52:56 -08:00
Cleroth
040dc2a5d4 small typo in syntax.rst
0x1e was misread as 0x13, it looks like
2022-12-23 10:29:14 -08:00
Victor Zverovich
6a186bcd66 Localize FMT_USE_LOCAL_TIME 2022-12-21 14:52:52 -08:00
Shawn Zhong
8c56919bd2 Check chrono spec starts with % 2022-12-21 14:40:30 -08:00
Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)
115001a3b1 Formatting of system clocks ought to be to UTC, not to local time.
This improves standards conformance of fmt.
2022-12-21 14:23:02 -08:00
Victor Zverovich
b90895412f Fix formatting of named arguments with locale 2022-12-15 09:59:40 -08:00
Vladislav Shchapov
d072f1dc69 Fix for issue #3228
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-14 13:49:36 -08:00
Victor Zverovich
3999fd193a Workaround an ADL issue 2022-12-11 09:32:17 -08:00
Vladislav Shchapov
c06e0b4ede Extract timezone offset from timezone conversion functions
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-10 10:13:53 -08:00
Vladislav Shchapov
1bf302a4ea Implement %Ez, %Oz for chrono formatter
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-12-10 10:13:53 -08:00
Victor Zverovich
f1733afd49 Pin godbolt example to specific version 2022-12-10 09:35:55 -08:00
Victor Zverovich
f61dcccc6e Update README.rst 2022-12-06 11:52:37 -08:00
Victor Zverovich
f9bcbdcbcf Update README.rst 2022-12-06 11:51:59 -08:00
Victor Zverovich
1a854b4aa5 Clarify what mod_inv_5 is 2022-12-04 08:36:03 -08:00
Radek Brich
62ceb181b1 fix #3105 - Compile-time error when mixing named argument with automatic indexing 2022-12-04 08:13:26 -08:00
Maksymilian Czudziak
b0c8263cb2 include/fmt/core.h: copy constructors removal 2022-11-30 16:44:42 -08:00
Vladislav Shchapov
d24be2e95c Add countl_zero function
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-30 12:25:01 -08:00
Victor Zverovich
8d50d814db Fix a chrono formatting issue found by fuzzing 2022-11-30 11:04:51 -08:00
Vladislav Shchapov
115ca96e0e Bump tested CMake version to 3.25
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-30 08:09:32 -08:00
Vladislav Shchapov
886491625d Remove workaround for GTest bug
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-30 07:16:37 -08:00
Vladislav Shchapov
74c51ff37e Skip only strptime dependent test in scan-test
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-27 08:59:30 -08:00
Chris Thrasher
69ffedfe52 Use target_compile_features to specify C++ standard requirement 2022-11-26 11:03:17 -08:00
Victor Zverovich
fae6f7e081 Optimize range formatter 2022-11-26 08:50:46 -08:00
Victor Zverovich
a69e43c9d7 Update benchmark results 2022-11-25 09:52:02 -08:00
Vladislav Shchapov
91c024ed33 Rename leading_v -> leading_xdigit
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-25 09:08:40 -08:00
David Korczynski
649aa102d6 CI linux: add CIFuzz Github action
Signed-off-by: David Korczynski <david@adalogics.com>
2022-11-24 08:22:51 -08:00
Vladislav Shchapov
31364732dc Replace snprintf-based hex float formatter with internal implementation
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-24 07:15:46 -08:00
Vladislav Shchapov
74d55a4938 Add missing operators
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-24 07:15:46 -08:00
Vladislav Shchapov
8276f1a204 Fix warning: the implicit by-copy capture of "this" is deprecated (EDG frontend)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-22 10:51:00 -08:00
Vladislav Shchapov
81ebe70b9b Fix warning: a class type that is not trivially copyable passed through ellipsis (EDG frontend)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-22 10:51:00 -08:00
Vladislav Shchapov
3160847ebd Enable C++17 tests on macOS
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-18 08:22:07 -08:00
Vladislav Shchapov
6a95f8c7eb Add missing env CTEST_OUTPUT_ON_FAILURE
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-18 08:22:07 -08:00
thesmurph
c7980542d3 Skip the scan-test if strptime isn't defined (#3184)
cygwin and embedded systems. By default newlib doesn't provide strptime
in time.h because it was added in a later X/Open versions. Issue: #3178
2022-11-16 09:42:01 -08:00
Hans-Martin B. Jensen
7df30f91ae Format unique_ptr with custom deleter (#3177)
* Format unique_ptr with custom deleter

Added deleter type to fmt::ptr unique_ptr overload. Deleter type is
part of the unique_ptr type.

* Review: apply clang-format

Co-authored-by: Hans-Martin B. Jensen <haje@eposaudio.com>
2022-11-13 21:54:32 -08:00
Victor Zverovich
d2e89c8b08 Document more chrono specs 2022-11-08 19:35:34 -10:00
Victor Zverovich
bd19593204 Document more chrono specs 2022-11-08 16:22:23 -10:00
Victor Zverovich
fd0d0ec8df Document more chrono specs 2022-11-08 14:57:57 -10:00
Victor Zverovich
8e93434edd Update README.rst 2022-11-08 11:21:16 -08:00
Victor Zverovich
fc07217d85 Make utf-8 detection compatible with gbk 2022-11-02 15:42:47 -07:00
Vladislav Shchapov
cb7373b469 Replace format with FMT_STRING. (#3162)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-11-02 13:17:16 -07:00
Stepan Ponomaryov
795ed8abf5 Add precision modifier for seconds in chrono format (#3148)
Co-authored-by: Stepan Ponomarev <stepan.ponomarev@itiviti.com>
2022-11-02 11:58:51 -07:00
Barry Revzin
66d71a1b35 Fixing formatting of range of range of char. (#3158) 2022-11-02 11:04:54 -07:00
Ihor Dutchak
80f8d34427 fmt::ostream - aggregate buffer instead of inheriting it (#3139)
Some MSVC-specific behavior:
When class fmt::ostream inherits detail::buffer - the last gets implicitly exported when fmt is built as a shared library.
Unless os.h is included, the compiler assumes detail::buffer is not externally exported and instantiates a local copy of it, which causes ODR violation.
With aggregation - there is no extra exporting of detail::buffer symbols.
2022-10-23 07:21:36 -07:00
Stepan Ponomaryov
64965bdc96 Add locale getter in tm_writer (#3147)
* Add locale getter in tm_writer

* Apply clang-format

Co-authored-by: Stepan Ponomarev <stepan.ponomarev@itiviti.com>
2022-10-20 10:22:03 -07:00
Vladislav Shchapov
e1ffa7655d Fix warning: conditional expression is constant. (#3150)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-10-20 07:49:53 -07:00
Federico Razzoli
8c19bf3f2f Mention MariaDB amongst the projects that use fmt (#3145) 2022-10-17 13:33:05 -07:00
Vladislav Shchapov
f67dbc9811 Remove duplicate implementation (#3144)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-10-17 11:15:28 -07:00
Vladislav Shchapov
cd7202e039 Fix overflow error (#3143)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-10-16 14:04:55 -07:00
Vladislav Shchapov
51d3685efe Remove duplicate template parameter. (#3142)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-10-14 16:34:24 -07:00
Patrick Roocks
9254cfa6f0 Support formatting of subseconds (#3115)
* Timestamp formatting shall print also subseconds, fixed a bug for fractional durations
2022-10-12 14:33:53 -07:00
Ihor Dutchak
cfb34a0607 Avoid using uint as a type name (#3137)
Sometime `uint` is defined as a global type by the project's code directly or by some 3rdparty libraries (e.g. Qt or OpenCV).
Some versions of MSVC (e.g. v16.11.15) gives a type shadowing warning:
```
3rdparty\fmtlib\fmt\include\fmt/format.h(3251): warning C4459: declaration of 'uint' hides global declaration
opencv2/core/hal/interface.h(45): note: see declaration of 'uint'
```
This also causes a compilation failure when `/WX` is used.
2022-10-12 10:53:47 -07:00
Tinson Lai
5ad7b71381 Fix options for C++20 experimental module in CMake (#3134)
* Fix options for C++20 experimental module in CMake

* Replace `FMT_CAN_MODULE` by `FMT_MODULE` in test/CMakeLists.txt
2022-10-12 08:23:12 -07:00
Vladislav Shchapov
d2c47c0df2 Fix broken condition (#3129)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-10-07 13:46:45 -07:00
Victor Zverovich
491c32cbd9 Workaround gcc bug 103879 2022-10-05 21:12:38 -07:00
Vladislav Shchapov
662adf4f33 Move formatter<std::error_code> from fmt/os.h to fmt/std.h (#3125)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-10-01 17:46:09 -07:00
Vladislav Shchapov
ad91cab374 Normalization of stdlib inline namespace names (#3119)
* Normalization of stdlib inline namespace names

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

* Remove all subnamespaces with names matching "__*" mask

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-29 09:52:38 -07:00
Vladislav Shchapov
0ccaed3a6c Set CMAKE_RUNTIME_OUTPUT_DIRECTORY relative to CMAKE_CURRENT_BINARY_DIR instead of CMAKE_BINARY_DIR (#3120)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-28 16:51:23 -07:00
Patrick Roocks
ad719619cc Support formatting of std time_point with utc_clock (#3110) 2022-09-28 07:31:53 -07:00
Dimitrij Mijoski
6e0a5f7fba Update CI to Ubuntu 20.04 and to newer versions of actions 2022-09-27 15:06:12 -07:00
huangqinjin
48f525d025 Add basic_format_string::get() 2022-09-22 19:50:04 -07:00
Victor Zverovich
0b5cb18b71 Use buffering in to_string to avoid bloat 2022-09-21 17:11:43 -07:00
Victor Zverovich
4c4f99a583 Update a godbolt link 2022-09-21 12:05:57 -07:00
Victor Zverovich
3272a7a3ce Update an example 2022-09-21 12:03:18 -07:00
Victor Zverovich
a48e3355a6 Improve docs 2022-09-18 08:44:04 -07:00
Victor Zverovich
afcf424294 Update docs 2022-09-16 15:27:18 -07:00
Victor Zverovich
ac85afaab6 Simplify format_error 2022-09-16 14:25:14 -07:00
Victor Zverovich
3178bb9a26 Update docs 2022-09-16 10:29:53 -07:00
Victor Zverovich
cf58f64c54 Update docs 2022-09-16 10:28:00 -07:00
Victor Zverovich
e4e0ae3918 Use fmt/core.h in examples 2022-09-15 20:41:32 -07:00
Victor Zverovich
d65acc4e6c Improve docs 2022-09-14 10:59:50 -07:00
Victor Zverovich
c9f790b061 Update docs 2022-09-14 10:58:52 -07:00
Victor Zverovich
6b8144a5ac Update locale docs 2022-09-14 06:38:29 -07:00
Björn Schäpers
2d66ad5d33 Suppress -Wshadow
Solves:
/fmt/include/fmt/ostream.h:89:18: warning: declaration of 'fbuf' shadows a previous local [-Wshadow]
   89 |   else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
      |                  ^~~~
C:/GIT/ok-mimot/libs/3rdParty/fmt/include/fmt/ostream.h:87:13: note: shadowed declaration is here
   87 |   if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
      |             ^~~~
2022-09-13 11:14:30 -07:00
Björn Schäpers
042af53324 Suppress -Wfloat-equal
Only NaN and Inf are not less than Inf and the check for NaN is done
before.

Solves:
.../fmt/include/fmt/format.h:2509:43: warning: comparing floating-point with '==' or '!=' is unsafe [-Wfloat-equal]
 2509 |     return !detail::isnan(value) && value != inf && value != -inf;
2022-09-13 11:14:30 -07:00
Victor Zverovich
192859c2b5 Optimize writing to buffers via back_insert_iterator 2022-09-12 15:32:12 -07:00
Sergiu Deitsch
e2f6d7665b fix gcc <= 7.1 compile errors 2022-09-12 10:43:16 -07:00
Vladislav Shchapov
61844b6b67 Fix build error on GCC-9
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-12 07:02:29 -07:00
Vladislav Shchapov
7a752e75ff New CI: GCC-9
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-12 07:02:29 -07:00
Victor Zverovich
94ceb38a09 Improve locale API 2022-09-11 09:33:31 -07:00
Victor Zverovich
58c4c012fa Disable slow windows build and simplify write_loc 2022-09-11 08:35:09 -07:00
Victor Zverovich
c3494ae364 Refactor float localization 2022-09-11 07:47:27 -07:00
Andy Maloney
8ae56161c8 Fix compilation with FMT_ENFORCE_COMPILE_STRING and FMT_WERROR (#3091) 2022-09-10 18:05:10 -07:00
Victor Zverovich
76705fc2ee Update doc 2022-09-10 15:06:09 -07:00
Vladislav Shchapov
21c2137e77 Add class name output to formatter for std::exception (#3076)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-10 08:04:00 -07:00
Victor Zverovich
ecffca6726 Don't parse '}' as fill 2022-09-07 17:33:31 -07:00
gerboengels
3176e0fad7 Add locale overload for formatted_size (#3084) (#3087)
Co-authored-by: Gerbo Engels <gerbo.engels@ortec-finance.com>
2022-09-07 14:15:12 -07:00
VinaCC
1feb430faa Fix intellisense on Windows (#3082)
__INTELLISENSE__ is 1 on vs2022 and clang, causing FMT_HAS_INCLUDE, FMT_USE_FCNTL, etc to be 0.
That results in VS and VSCode having a lot of linter errors while code compiles just fine.
2022-09-05 11:50:06 -07:00
Victor Zverovich
b98ffb7dbd Improve locale handling 2022-09-04 21:07:30 -07:00
Vladislav Shchapov
bac53951b8 Add starts_with to basic_string_view. (#3080)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-04 11:41:16 -07:00
Victor Zverovich
d59b89e9cd More locale 2022-09-04 11:23:45 -07:00
Victor Zverovich
58a5563a9f Implement grouping 2022-09-04 09:01:26 -07:00
Victor Zverovich
1b94271ff6 Add support for UTF-8 digit separators 2022-09-03 11:01:05 -07:00
Victor Zverovich
768d79a839 Implement format_facet 2022-09-03 09:42:36 -07:00
Victor Zverovich
91ecb38a34 Localize negative integers 2022-09-03 07:01:11 -07:00
Victor Zverovich
aec3bb5d0a Workaround C complex.h idiocy 2022-09-03 06:35:55 -07:00
NewbieOrange
29c6000137 Simplify is_variant_like_ check, fix compile error before GCC 11 (#3072)
Co-authored-by: Vladislav Shchapov <vladislav@shchapov.ru>

Co-authored-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-02 21:08:07 -07:00
Victor Zverovich
fec5515c55 num_format_facet -> format_facet 2022-09-02 18:55:08 -07:00
Victor Zverovich
f187274d36 Add loc_value 2022-09-02 13:45:23 -07:00
Victor Zverovich
fc5e59fe4a Don't use stringstream 2022-09-02 13:05:26 -07:00
Victor Zverovich
d6a8704605 Improve locale support 2022-09-02 11:52:19 -07:00
Victor Zverovich
56c72a671c Reduce locale dependency 2022-09-02 10:22:11 -07:00
Zach Toogood
4191477b98 Add formatter for std::exception (#3062)
Co-authored-by: fekir <federico.kircheis@gmail.com>
Co-authored-by: Alexey Ochapov <alexez@alexez.com>
Co-authored-by: Vladislav Shchapov <vladislav@shchapov.ru>

Co-authored-by: fekir <federico.kircheis@gmail.com>
Co-authored-by: Alexey Ochapov <alexez@alexez.com>
Co-authored-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-09-02 08:33:37 -07:00
Victor Zverovich
75383a87f9 Inline trivial functions 2022-09-01 18:25:23 -07:00
Victor Zverovich
48327a82e3 Make format.h compile faster 2022-09-01 17:06:47 -07:00
Victor Zverovich
b79ed4105a Remove unnecessary type_identity 2022-09-01 16:29:08 -07:00
Victor Zverovich
64e29893cf Improve locale support 2022-09-01 14:48:43 -07:00
Victor Zverovich
0b0f7cfbfc hip workaround 2022-09-01 09:18:53 -07:00
Greg Sjaardema
40e414d823 Fix compilation error with gcc-7.2.0
Without the added line, the gcc-7.2.0 compiler will give the following error:
```
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/format.h:1240:8: error: uninitialized variable 'buffer' in 'constexpr' function
   Char buffer[digits10<UInt>() + 1];
        ^~~~~~
```
See https://godbolt.org/z/fh7TMs9qs
2022-08-30 10:55:57 -07:00
Vladislav Shchapov
33b4c33c5b Requires FMT_CONSTEXPR20 support
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-08-29 13:33:48 -07:00
Vladislav Shchapov
a07411c2b9 Disable compile-time checks for dynamic width/precision test for LCC and compiler without std::is_constant_evaluated()
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-08-29 13:33:48 -07:00
Vladislav Shchapov
797d82b21a Disable non-type template args for LCC
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-08-29 13:33:48 -07:00
Vladislav Shchapov
a553521d6d Disable "GCC optimize" pragma for LCC
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2022-08-29 13:33:48 -07:00
65 changed files with 4969 additions and 3466 deletions

View File

@@ -1,6 +1,7 @@
<!--
Please read the contribution guidelines before submitting a pull request:
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
By submitting this pull request, you agree that your contributions are licensed
under the {fmt} license, and agree to future changes to the licensing.
By submitting this pull request, you agree to license your contribution(s)
under the terms outlined in LICENSE.rst and represent that you have the right
to do so.
-->

30
.github/workflows/cifuzz.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: CIFuzz
on: [pull_request]
permissions:
contents: read
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'fmt'
dry-run: false
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'fmt'
fuzz-seconds: 300
dry-run: false
language: c++
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View File

@@ -11,7 +11,15 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Add ubuntu mirrors
run: |
# Github Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
- name: Create Build Environment
run: |

View File

@@ -7,66 +7,83 @@ permissions:
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-20.04
strategy:
matrix:
cxx: [g++-4.8, g++-10, clang++-9]
build_type: [Debug, Release]
std: [11]
os: [ubuntu-18.04]
include:
- cxx: g++-4.8
install: sudo apt install g++-4.8
os: ubuntu-18.04
- cxx: g++-8
build_type: Debug
std: 14
install: sudo apt install g++-8
os: ubuntu-18.04
- cxx: g++-8
build_type: Debug
std: 17
install: sudo apt install g++-8
os: ubuntu-18.04
- cxx: g++-9
build_type: Debug
std: 17
- cxx: g++-10
build_type: Debug
std: 17
os: ubuntu-18.04
- cxx: g++-11
build_type: Debug
std: 20
os: ubuntu-20.04
install: sudo apt install g++-11
- cxx: clang++-8
build_type: Debug
std: 17
cxxflags: -stdlib=libc++
os: ubuntu-18.04
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
- cxx: clang++-9
install: sudo apt install clang-9
- cxx: clang++-9
build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17
os: ubuntu-18.04
install: sudo apt install clang-9
- cxx: clang++-11
build_type: Debug
std: 20
os: ubuntu-20.04
- cxx: clang++-11
build_type: Debug
std: 20
cxxflags: -stdlib=libc++
os: ubuntu-20.04
install: sudo apt install libc++-11-dev libc++abi-11-dev
- shared: -DBUILD_SHARED_LIBS=ON
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set timezone
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
- name: Add repositories for older GCC
run: |
# Below two repos provide GCC 4.8, 5.5 and 6.4
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main'
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe'
# Below two repos additionally update GCC 6 to 6.5
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main'
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe'
if: ${{ matrix.cxx == 'g++-4.8' }}
- name: Add ubuntu mirrors
run: |
# Github Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
- name: Create Build Environment
run: |
${{matrix.install}}
sudo apt update
${{matrix.install}}
sudo apt install locales-all
cmake -E make_directory ${{runner.workspace}}/build

View File

@@ -7,15 +7,19 @@ permissions:
jobs:
build:
runs-on: macos-10.15
runs-on: macos-11
strategy:
matrix:
build_type: [Debug, Release]
std: [11, 17]
include:
- shared: -DBUILD_SHARED_LIBS=ON
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set timezone
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@@ -24,6 +28,7 @@ jobs:
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE

View File

@@ -41,7 +41,10 @@ jobs:
standard: 20
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@@ -75,14 +78,17 @@ jobs:
shell: msys2 {0}
strategy:
matrix:
sys: [ mingw64, mingw32, ucrt64 ]
sys: [ mingw64, ucrt64 ]
steps:
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
shell: cmd
- uses: msys2/setup-msys2@v2
with:
release: false
msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Configure
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
env: { LDFLAGS: -fuse-ld=lld }
@@ -90,3 +96,5 @@ jobs:
run: cmake --build ../build
- name: Test
run: ctest -j `nproc` --test-dir ../build
env:
CTEST_OUTPUT_ON_FAILURE: True

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.26)
# Fallback for using newer policies on CMake <3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
@@ -24,15 +24,86 @@ function(join result_var)
set(${result_var} "${result}" PARENT_SCOPE)
endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target)
if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif ()
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endfunction()
# Adds a library compiled with C++20 module support.
# `enabled` is a CMake variables that specifies if modules are enabled.
# If modules are disabled `add_module_library` falls back to creating a
# non-modular library.
#
# Usage:
# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled])
function(add_module_library name)
cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN})
set(sources ${AML_UNPARSED_ARGUMENTS})
add_library(${name})
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX)
if (NOT ${${AML_IF}})
# Create a non-modular library.
target_sources(${name} PRIVATE ${AML_FALLBACK})
return()
endif ()
# Modules require C++20.
target_compile_features(${name} PUBLIC cxx_std_20)
if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
# `std` is affected by CMake options and may be higher than C++20.
get_target_property(std ${name} CXX_STANDARD)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(pcms)
foreach (src ${sources})
get_filename_component(pcm ${src} NAME_WE)
set(pcm ${pcm}.pcm)
# Propagate -fmodule-file=*.pcm to targets that link with this library.
target_compile_options(
${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm})
# Use an absolute path to prevent target_link_libraries prepending -l
# to it.
set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm})
add_custom_command(
OUTPUT ${pcm}
COMMAND ${CMAKE_CXX_COMPILER}
-std=c++${std} -x c++-module --precompile -c
-o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src}
"-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>"
# Required by the -I generator expression above.
COMMAND_EXPAND_LISTS
DEPENDS ${src})
endforeach ()
# Add .pcm files as sources to make sure they are built before the library.
set(sources)
foreach (pcm ${pcms})
get_filename_component(pcm_we ${pcm} NAME_WE)
set(obj ${pcm_we}.o)
# Use an absolute path to prevent target_link_libraries prepending -l.
set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj})
add_custom_command(
OUTPUT ${obj}
COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS>
-c -o ${obj} ${pcm}
DEPENDS ${pcm})
endforeach ()
endif ()
target_sources(${name} PRIVATE ${sources})
endfunction()
include(CMakeParseArguments)
@@ -75,7 +146,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
# Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ON)
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@@ -83,16 +154,6 @@ option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
set(FMT_CAN_MODULE OFF)
if (CMAKE_CXX_STANDARD GREATER 17 AND
# msvc 16.10-pre4
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
set(FMT_CAN_MODULE OFF)
endif ()
if (NOT FMT_CAN_MODULE)
set(FMT_MODULE OFF)
message(STATUS "Module support is disabled.")
endif ()
if (FMT_TEST AND FMT_MODULE)
# The tests require {fmt} to be compiled as traditional library
message(STATUS "Testing is incompatible with build mode 'module'.")
@@ -101,6 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
if (FMT_SYSTEM_HEADERS)
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
endif ()
if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
set(FMT_TEST OFF)
message(STATUS "MSDOS is incompatible with gtest")
endif()
# Get version from core.h
file(READ include/fmt/core.h core_h)
@@ -118,23 +183,15 @@ message(STATUS "Version: ${FMT_VERSION}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif ()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(cxx14)
include(CheckCXXCompilerFlag)
include(JoinPaths)
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
if (${index} GREATER -1)
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
# compatibility with older CMake versions.
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
endif ()
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
"Preset for the export of private symbols")
@@ -220,16 +277,18 @@ endfunction()
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h)
if (FMT_MODULE)
set(FMT_SOURCES src/fmt.cc)
elseif (FMT_OS)
set(FMT_SOURCES src/format.cc src/os.cc)
else()
set(FMT_SOURCES src/format.cc)
set(FMT_SOURCES src/format.cc)
if (FMT_OS)
set(FMT_SOURCES ${FMT_SOURCES} src/os.cc)
endif ()
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
add_module_library(fmt src/fmt.cc FALLBACK
${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst
IF FMT_MODULE)
add_library(fmt::fmt ALIAS fmt)
if (FMT_MODULE)
enable_module(fmt)
endif ()
if (FMT_WERROR)
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
@@ -237,11 +296,8 @@ endif ()
if (FMT_PEDANTIC)
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
endif ()
if (FMT_MODULE)
enable_module(fmt)
endif ()
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
target_compile_features(fmt PUBLIC cxx_std_11)
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
@@ -262,7 +318,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif ()
if (BUILD_SHARED_LIBS)
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
endif ()
if (FMT_SAFE_DURATION_CAST)
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
@@ -272,7 +328,7 @@ add_library(fmt-header-only INTERFACE)
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
@@ -339,8 +395,6 @@ if (FMT_INSTALL)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::)
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
endif ()

View File

@@ -1,3 +1,434 @@
10.0.0 - 2023-05-09
-------------------
* Replaced Grisu with a new floating-point formatting algorithm for given
precision (`#3262 <https://github.com/fmtlib/fmt/issues/3262>`_,
`#2750 <https://github.com/fmtlib/fmt/issues/2750>`_,
`#3269 <https://github.com/fmtlib/fmt/pull/3269>`_,
`#3276 <https://github.com/fmtlib/fmt/pull/3276>`_).
The new algorithm is based on Dragonbox already used for the
shortest representation and gives substantial performance improvement:
.. image:: https://user-images.githubusercontent.com/33922675/
211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png
* Red: new algorithm
* Green: new algorithm with ``FMT_USE_FULL_CACHE_DRAGONBOX`` defined to 1
* Blue: old algorithm
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
* Replaced ``snprintf``-based hex float formatter with an internal
implementation (`#3179 <https://github.com/fmtlib/fmt/pull/3179>`_,
`#3203 <https://github.com/fmtlib/fmt/pull/3203>`_).
This removes the last usage of ``s(n)printf`` in {fmt}.
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Fixed alignment of floating-point numbers with localization
(`#3263 <https://github.com/fmtlib/fmt/issues/3263>`_,
`#3272 <https://github.com/fmtlib/fmt/pull/3272>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Improved C++20 module support
(`#3134 <https://github.com/fmtlib/fmt/pull/3134>`_,
`#3254 <https://github.com/fmtlib/fmt/pull/3254>`_,
`#3386 <https://github.com/fmtlib/fmt/pull/3386>`_,
`#3387 <https://github.com/fmtlib/fmt/pull/3387>`_,
`#3388 <https://github.com/fmtlib/fmt/pull/3388>`_,
`#3392 <https://github.com/fmtlib/fmt/pull/3392>`_,
`#3397 <https://github.com/fmtlib/fmt/pull/3397>`_,
`#3399 <https://github.com/fmtlib/fmt/pull/3399>`_,
`#3400 <https://github.com/fmtlib/fmt/pull/3400>`_).
Thanks `@laitingsheng (Tinson Lai) <https://github.com/laitingsheng>`_,
`@Orvid (Orvid King) <https://github.com/Orvid>`_,
`@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
Switched to the `modules CMake library <https://github.com/vitaut/modules>`_
which allows building {fmt} as a C++20 module with clang::
CXX=clang++ cmake -DFMT_MODULE=ON .
make
* Made ``format_as`` work with any user-defined type and not just enums.
For example (`godbolt <https://godbolt.org/z/b7rqhq5Kh>`__):
.. code:: c++
#include <fmt/format.h>
struct floaty_mc_floatface {
double value;
};
auto format_as(floaty_mc_floatface f) { return f.value; }
int main() {
fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42"
}
* Removed deprecated implicit conversions for enums and conversions to primitive
types for compatibility with ``std::format`` and to prevent potential ODR
violations. Use ``format_as`` instead.
* Added support for fill, align and width to the time point formatter
(`#3237 <https://github.com/fmtlib/fmt/issues/3237>`_,
`#3260 <https://github.com/fmtlib/fmt/pull/3260>`_,
`#3275 <https://github.com/fmtlib/fmt/pull/3275>`_).
For example (`godbolt <https://godbolt.org/z/rKP6MGz6c>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
// prints " 2023"
fmt::print("{:>8%Y}\n", std::chrono::system_clock::now());
}
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Implemented formatting of subseconds
(`#2207 <https://github.com/fmtlib/fmt/issues/2207>`_,
`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
`#3115 <https://github.com/fmtlib/fmt/pull/3115>`_,
`#3143 <https://github.com/fmtlib/fmt/pull/3143>`_,
`#3144 <https://github.com/fmtlib/fmt/pull/3144>`_,
`#3349 <https://github.com/fmtlib/fmt/pull/3349>`_).
For example (`godbolt <https://godbolt.org/z/45738oGEo>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
// prints 01.234567
fmt::print("{:%S}\n", std::chrono::microseconds(1234567));
}
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Added precision support to ``%S``
(`#3148 <https://github.com/fmtlib/fmt/pull/3148>`_).
Thanks `@SappyJoy (Stepan Ponomaryov) <https://github.com/SappyJoy>`_
* Added support for ``std::utc_time``
(`#3098 <https://github.com/fmtlib/fmt/issues/3098>`_,
`#3110 <https://github.com/fmtlib/fmt/pull/3110>`_).
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_.
* Switched formatting of ``std::chrono::system_clock`` from local time to UTC
for compatibility with the standard
(`#3199 <https://github.com/fmtlib/fmt/issues/3199>`_,
`#3230 <https://github.com/fmtlib/fmt/pull/3230>`_).
Thanks `@ned14 (Niall Douglas) <https://github.com/ned14>`_.
* Added support for ``%Ez`` and ``%Oz`` to chrono formatters.
(`#3220 <https://github.com/fmtlib/fmt/issues/3220>`_,
`#3222 <https://github.com/fmtlib/fmt/pull/3222>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Improved validation of format specifiers for ``std::chrono::duration``
(`#3219 <https://github.com/fmtlib/fmt/issues/3219>`_,
`#3232 <https://github.com/fmtlib/fmt/pull/3232>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Fixed formatting of time points before the epoch
(`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
`#3261 <https://github.com/fmtlib/fmt/pull/3261>`_).
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
auto t = std::chrono::system_clock::from_time_t(0) -
std::chrono::milliseconds(250);
fmt::print("{:%S}\n", t); // prints 59.750000000
}
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Experimental: implemented glibc extension for padding seconds, minutes and
hours (`#2959 <https://github.com/fmtlib/fmt/issues/2959>`_,
`#3271 <https://github.com/fmtlib/fmt/pull/3271>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Added a formatter for ``std::exception``
(`#2977 <https://github.com/fmtlib/fmt/issues/2977>`_,
`#3012 <https://github.com/fmtlib/fmt/issues/3012>`_,
`#3062 <https://github.com/fmtlib/fmt/pull/3062>`_,
`#3076 <https://github.com/fmtlib/fmt/pull/3076>`_,
`#3119 <https://github.com/fmtlib/fmt/pull/3119>`_).
For example (`godbolt <https://godbolt.org/z/8xoWGs9e4>`__):
.. code:: c++
#include <fmt/std.h>
#include <vector>
int main() {
try {
std::vector<bool>().at(0);
} catch(const std::exception& e) {
fmt::print("{}", e);
}
}
prints::
vector<bool>::_M_range_check: __n (which is 0) >= this->size() (which is 0)
on libstdc++.
Thanks `@zach2good (Zach Toogood) <https://github.com/zach2good>`_ and
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Moved ``std::error_code`` formatter from ``fmt/os.h`` to ``fmt/std.h``.
(`#3125 <https://github.com/fmtlib/fmt/pull/3125>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added formatters for standard container adapters: ``std::priority_queue``,
``std::queue`` and ``std::stack``
(`#3215 <https://github.com/fmtlib/fmt/issues/3215>`_,
`#3279 <https://github.com/fmtlib/fmt/pull/3279>`_).
For example (`godbolt <https://godbolt.org/z/74h1xY9qK>`__):
.. code:: c++
#include <fmt/ranges.h>
#include <stack>
#include <vector>
int main() {
auto s = std::stack<bool, std::vector<bool>>();
for (auto b: {true, false, true}) s.push(b);
fmt::print("{}\n", s); // prints [true, false, true]
}
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Added a formatter for ``std::optional`` to ``fmt/std.h``.
Thanks `@tom-huntington <https://github.com/tom-huntington>`_.
* Fixed formatting of valueless by exception variants
(`#3347 <https://github.com/fmtlib/fmt/pull/3347>`_).
Thanks `@TheOmegaCarrot <https://github.com/TheOmegaCarrot>`_.
* Made ``fmt::ptr`` accept ``unique_ptr`` with a custom deleter
(`#3177 <https://github.com/fmtlib/fmt/pull/3177>`_).
Thanks `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
* Fixed formatting of noncopyable ranges and nested ranges of chars
(`#3158 <https://github.com/fmtlib/fmt/pull/3158>`_
`#3286 <https://github.com/fmtlib/fmt/issues/3286>`_,
`#3290 <https://github.com/fmtlib/fmt/pull/3290>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Fixed issues with formatting of paths and ranges of paths
(`#3319 <https://github.com/fmtlib/fmt/issues/3319>`_,
`#3321 <https://github.com/fmtlib/fmt/pull/3321>`_
`#3322 <https://github.com/fmtlib/fmt/issues/3322>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Improved handling of invalid Unicode in paths.
* Enabled compile-time checks on Apple clang 14 and later
(`#3331 <https://github.com/fmtlib/fmt/pull/3331>`_).
Thanks `@cloyce (Cloyce D. Spradling) <https://github.com/cloyce>`_.
* Improved compile-time checks of named arguments
(`#3105 <https://github.com/fmtlib/fmt/issues/3105>`_,
`#3214 <https://github.com/fmtlib/fmt/pull/3214>`_).
Thanks `@rbrich (Radek Brich) <https://github.com/rbrich>`_.
* Fixed formatting when both alignment and ``0`` are given
(`#3236 <https://github.com/fmtlib/fmt/issues/3236>`_,
`#3248 <https://github.com/fmtlib/fmt/pull/3248>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Improved Unicode support in the experimental file API on Windows
(`#3234 <https://github.com/fmtlib/fmt/issues/3234>`_,
`#3293 <https://github.com/fmtlib/fmt/pull/3293>`_).
Thanks `@Fros1er (Froster) <https://github.com/Fros1er>`_.
* Unified UTF transcoding
(`#3416 <https://github.com/fmtlib/fmt/pull/3416>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added support for UTF-8 digit separators via an experimental locale facet
(`#1861 <https://github.com/fmtlib/fmt/issues/1861>`_).
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
.. code:: c++
auto loc = std::locale(
std::locale(), new fmt::format_facet<std::locale>(""));
auto s = fmt::format(loc, "{:L}", 1000);
where ```` is U+2019 used as a digit separator in the de_CH locale.
* Added an overload of ``formatted_size`` that takes a locale
(`#3084 <https://github.com/fmtlib/fmt/issues/3084>`_,
`#3087 <https://github.com/fmtlib/fmt/pull/3087>`_).
Thanks `@gerboengels <https://github.com/gerboengels>`_.
* Removed the deprecated ``FMT_DEPRECATED_OSTREAM``.
* Fixed a UB when using a null ``std::string_view`` with ``fmt::to_string``
or format string compilation
(`#3241 <https://github.com/fmtlib/fmt/issues/3241>`_,
`#3244 <https://github.com/fmtlib/fmt/pull/3244>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added ``starts_with`` to the fallback ``string_view`` implementation
(`#3080 <https://github.com/fmtlib/fmt/pull/3080>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added ``fmt::basic_format_string::get()`` for compatibility with
``basic_format_string`` (`#3111 <https://github.com/fmtlib/fmt/pull/3111>`_).
Thanks `@huangqinjin <https://github.com/huangqinjin>`_.
* Added ``println`` for compatibility with C++23
(`#3267 <https://github.com/fmtlib/fmt/pull/3267>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Improved documentation
(`#3108 <https://github.com/fmtlib/fmt/issues/3108>`_,
`#3169 <https://github.com/fmtlib/fmt/issues/3169>`_,
`#3243 <https://github.com/fmtlib/fmt/pull/3243>`_).
`#3404 <https://github.com/fmtlib/fmt/pull/3404>`_).
Thanks `@Cleroth <https://github.com/Cleroth>`_ and
`@Vertexwahn <https://github.com/Vertexwahn>`_.
* Improved build configuration and tests
(`#3118 <https://github.com/fmtlib/fmt/pull/3118>`_,
`#3120 <https://github.com/fmtlib/fmt/pull/3120>`_,
`#3188 <https://github.com/fmtlib/fmt/pull/3188>`_,
`#3189 <https://github.com/fmtlib/fmt/issues/3189>`_,
`#3198 <https://github.com/fmtlib/fmt/pull/3198>`_,
`#3205 <https://github.com/fmtlib/fmt/pull/3205>`_,
`#3207 <https://github.com/fmtlib/fmt/pull/3207>`_,
`#3210 <https://github.com/fmtlib/fmt/pull/3210>`_,
`#3240 <https://github.com/fmtlib/fmt/pull/3240>`_,
`#3256 <https://github.com/fmtlib/fmt/pull/3256>`_,
`#3264 <https://github.com/fmtlib/fmt/pull/3264>`_,
`#3299 <https://github.com/fmtlib/fmt/issues/3299>`_,
`#3302 <https://github.com/fmtlib/fmt/pull/3302>`_,
`#3312 <https://github.com/fmtlib/fmt/pull/3312>`_,
`#3317 <https://github.com/fmtlib/fmt/issues/3317>`_,
`#3328 <https://github.com/fmtlib/fmt/pull/3328>`_,
`#3333 <https://github.com/fmtlib/fmt/pull/3333>`_,
`#3369 <https://github.com/fmtlib/fmt/pull/3369>`_,
`#3373 <https://github.com/fmtlib/fmt/issues/3373>`_,
`#3395 <https://github.com/fmtlib/fmt/pull/3395>`_,
`#3406 <https://github.com/fmtlib/fmt/pull/3406>`_,
`#3411 <https://github.com/fmtlib/fmt/pull/3411>`_).
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@DavidKorczynski <https://github.com/DavidKorczynski>`_,
`@ChrisThrasher (Chris Thrasher) <https://github.com/ChrisThrasher>`_,
`@FrancoisCarouge (François Carouge) <https://github.com/FrancoisCarouge>`_,
`@kennyweiss (Kenny Weiss) <https://github.com/kennyweiss>`_,
`@luzpaz <https://github.com/luzpaz>`_,
`@codeinred (Alecto Irene Perez) <https://github.com/codeinred>`_,
`@Mixaill (Mikhail Paulyshka) <https://github.com/Mixaill>`_,
`@joycebrum (Joyce) <https://github.com/joycebrum>`_,
`@kevinhwang (Kevin Hwang) <https://github.com/kevinhwang>`_,
`@Vertexwahn <https://github.com/Vertexwahn>`_.
* Fixed a regression in handling empty format specifiers after a colon (``{:}``)
(`#3086 <https://github.com/fmtlib/fmt/pull/3086>`_).
Thanks `@oxidase (Michael Krasnyk) <https://github.com/oxidase>`_.
* Worked around a broken implementation of ``std::is_constant_evaluated`` in
some versions of libstdc++ on clang
(`#3247 <https://github.com/fmtlib/fmt/issues/3247>`_,
`#3281 <https://github.com/fmtlib/fmt/pull/3281>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Fixed formatting of volatile variables
(`#3068 <https://github.com/fmtlib/fmt/pull/3068>`_).
* Fixed various warnings and compilation issues
(`#3057 <https://github.com/fmtlib/fmt/pull/3057>`_,
`#3066 <https://github.com/fmtlib/fmt/pull/3066>`_,
`#3072 <https://github.com/fmtlib/fmt/pull/3072>`_,
`#3082 <https://github.com/fmtlib/fmt/pull/3082>`_,
`#3091 <https://github.com/fmtlib/fmt/pull/3091>`_,
`#3092 <https://github.com/fmtlib/fmt/issues/3092>`_,
`#3093 <https://github.com/fmtlib/fmt/pull/3093>`_,
`#3095 <https://github.com/fmtlib/fmt/pull/3095>`_,
`#3096 <https://github.com/fmtlib/fmt/issues/3096>`_,
`#3097 <https://github.com/fmtlib/fmt/pull/3097>`_,
`#3128 <https://github.com/fmtlib/fmt/issues/3128>`_,
`#3129 <https://github.com/fmtlib/fmt/pull/3129>`_,
`#3137 <https://github.com/fmtlib/fmt/pull/3137>`_,
`#3139 <https://github.com/fmtlib/fmt/pull/3139>`_,
`#3140 <https://github.com/fmtlib/fmt/issues/3140>`_,
`#3142 <https://github.com/fmtlib/fmt/pull/3142>`_,
`#3149 <https://github.com/fmtlib/fmt/issues/3149>`_,
`#3150 <https://github.com/fmtlib/fmt/pull/3150>`_,
`#3154 <https://github.com/fmtlib/fmt/issues/3154>`_,
`#3163 <https://github.com/fmtlib/fmt/issues/3163>`_,
`#3178 <https://github.com/fmtlib/fmt/issues/3178>`_,
`#3184 <https://github.com/fmtlib/fmt/pull/3184>`_,
`#3196 <https://github.com/fmtlib/fmt/pull/3196>`_,
`#3204 <https://github.com/fmtlib/fmt/issues/3204>`_,
`#3206 <https://github.com/fmtlib/fmt/pull/3206>`_,
`#3208 <https://github.com/fmtlib/fmt/pull/3208>`_,
`#3213 <https://github.com/fmtlib/fmt/issues/3213>`_,
`#3216 <https://github.com/fmtlib/fmt/pull/3216>`_,
`#3224 <https://github.com/fmtlib/fmt/issues/3224>`_,
`#3226 <https://github.com/fmtlib/fmt/issues/3226>`_,
`#3228 <https://github.com/fmtlib/fmt/issues/3228>`_,
`#3229 <https://github.com/fmtlib/fmt/pull/3229>`_,
`#3259 <https://github.com/fmtlib/fmt/pull/3259>`_,
`#3274 <https://github.com/fmtlib/fmt/issues/3274>`_,
`#3287 <https://github.com/fmtlib/fmt/issues/3287>`_,
`#3288 <https://github.com/fmtlib/fmt/pull/3288>`_,
`#3292 <https://github.com/fmtlib/fmt/issues/3292>`_,
`#3295 <https://github.com/fmtlib/fmt/pull/3295>`_,
`#3296 <https://github.com/fmtlib/fmt/pull/3296>`_,
`#3298 <https://github.com/fmtlib/fmt/issues/3298>`_,
`#3325 <https://github.com/fmtlib/fmt/issues/3325>`_,
`#3326 <https://github.com/fmtlib/fmt/pull/3326>`_,
`#3334 <https://github.com/fmtlib/fmt/issues/3334>`_,
`#3342 <https://github.com/fmtlib/fmt/issues/3342>`_,
`#3343 <https://github.com/fmtlib/fmt/pull/3343>`_,
`#3351 <https://github.com/fmtlib/fmt/issues/3351>`_,
`#3352 <https://github.com/fmtlib/fmt/pull/3352>`_,
`#3362 <https://github.com/fmtlib/fmt/pull/3362>`_,
`#3365 <https://github.com/fmtlib/fmt/issues/3365>`_,
`#3366 <https://github.com/fmtlib/fmt/pull/3366>`_,
`#3374 <https://github.com/fmtlib/fmt/pull/3374>`_,
`#3377 <https://github.com/fmtlib/fmt/issues/3377>`_,
`#3378 <https://github.com/fmtlib/fmt/pull/3378>`_,
`#3381 <https://github.com/fmtlib/fmt/issues/3381>`_,
`#3398 <https://github.com/fmtlib/fmt/pull/3398>`_,
`#3413 <https://github.com/fmtlib/fmt/pull/3413>`_,
`#3415 <https://github.com/fmtlib/fmt/issues/3415>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@NewbieOrange <https://github.com/NewbieOrange>`_,
`@EngineLessCC (VivyaCC) <https://github.com/EngineLessCC>`_,
`@asmaloney (Andy Maloney) <https://github.com/asmaloney>`_,
`@HazardyKnusperkeks (Björn Schäpers)
<https://github.com/HazardyKnusperkeks>`_,
`@sergiud (Sergiu Deitsch) <https://github.com/sergiud>`_,
`@Youw (Ihor Dutchak) <https://github.com/Youw>`_,
`@thesmurph <https://github.com/thesmurph>`_,
`@czudziakm (Maksymilian Czudziak) <https://github.com/czudziakm>`_,
`@Roman-Koshelev <https://github.com/Roman-Koshelev>`_,
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
`@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_,
`@russelltg (Russell Greene) <https://github.com/russelltg>`_,
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
`@tmartin-gh <https://github.com/tmartin-gh>`_,
`@Zhaojun-Liu (June Liu) <https://github.com/Zhaojun-Liu>`_,
`@louiswins (Louis Wilson) <https://github.com/louiswins>`_,
`@mogemimi <https://github.com/mogemimi>`_.
9.1.0 - 2022-08-27
------------------
@@ -105,6 +536,7 @@
`#2982 <https://github.com/fmtlib/fmt/pull/2982>`_,
`#2985 <https://github.com/fmtlib/fmt/pull/2985>`_,
`#2988 <https://github.com/fmtlib/fmt/issues/2988>`_,
`#2989 <https://github.com/fmtlib/fmt/issues/2989>`_,
`#3000 <https://github.com/fmtlib/fmt/issues/3000>`_,
`#3006 <https://github.com/fmtlib/fmt/issues/3006>`_,
`#3014 <https://github.com/fmtlib/fmt/issues/3014>`_,
@@ -2260,7 +2692,7 @@
<https://github.com/kwesolowski>`_.
* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
macro to prevent interferring with fuzzing of projects using {fmt}
macro to prevent interfering with fuzzing of projects using {fmt}
(`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2012 - present, Victor Zverovich
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -47,7 +47,8 @@ Features
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
round-trip guarantees
round-trip guarantees using the `Dragonbox <https://github.com/jk-jeon/dragonbox>`_
algorithm
* Safe `printf implementation
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
extension for positional arguments
@@ -191,24 +192,24 @@ Speed tests
================= ============= ===========
Library Method Run Time, s
================= ============= ===========
libc printf 1.04
libc++ std::ostream 3.05
{fmt} 6.1.1 fmt::print 0.75
Boost Format 1.67 boost::format 7.24
Folly Format folly::format 2.23
libc printf 0.91
libc++ std::ostream 2.49
{fmt} 9.1 fmt::print 0.74
Boost Format 1.80 boost::format 6.26
Folly Format folly::format 1.87
================= ============= ===========
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``.
The above results were generated by building ``tinyformat_test.cpp`` on macOS
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
12.6.1 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
`ryu <https://github.com/ulfjack/ryu>`_:
@@ -322,8 +323,10 @@ Projects using this library
* `ccache <https://ccache.dev/>`_: a compiler cache
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: an analytical database
management system
* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle
@@ -360,6 +363,10 @@ Projects using this library
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
* `libunicode <https://github.com/contour-terminal/libunicode/>`_: a modern C++17 Unicode library
* `MariaDB <https://mariadb.org/>`_: relational database management system
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
research programming language for concurrent ownership
@@ -413,6 +420,9 @@ Projects using this library
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
MMORPG framework
* `🐙 userver framework <https://userver.tech/>`_: open-source asynchronous
framework with a rich set of abstractions and database drivers
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
terminal
@@ -523,8 +533,7 @@ Maintainers
-----------
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.
<https://github.com/vitaut>`_) 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

View File

@@ -31,7 +31,8 @@ Core API
``fmt/core.h`` defines the core API which provides main formatting functions
for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
dependencies for better compile times. This header is only beneficial when
using {fmt} as a library and not in the header-only mode.
using {fmt} as a library (the default) and not in the header-only mode.
It also provides ``formatter`` specializations for built-in and string types.
The following functions use :ref:`format string syntax <syntax>`
similar to that of Python's `str.format
@@ -70,23 +71,182 @@ checked at compile time in C++20. To pass a runtime format string wrap it in
Compile-Time Format String Checks
---------------------------------
Compile-time checks are enabled by default on compilers that support C++20
``consteval``. On older compilers you can use the ``FMT_STRING`` macro defined
in ``fmt/format.h`` instead. It requires C++14 and is a no-op in C++11.
Compile-time format string checks are enabled by default on compilers
that support C++20 ``consteval``. On older compilers you can use the
:ref:`FMT_STRING <legacy-checks>`: macro defined in ``fmt/format.h`` instead.
.. doxygendefine:: FMT_STRING
To force the use of legacy compile-time checks, define the preprocessor variable
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
will fail to compile with regular strings. Runtime-checked formatting is still
possible using ``fmt::vformat``, ``fmt::vprint``, etc.
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
.. doxygenclass:: fmt::basic_format_string
:members:
.. doxygentypedef:: fmt::format_string
.. doxygenfunction:: fmt::runtime(string_view) -> basic_runtime<char>
.. doxygenfunction:: fmt::runtime(string_view) -> runtime_format_string<>
.. _udt:
Formatting User-Defined Types
-----------------------------
The {fmt} library provides formatters for many standard C++ types.
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
and time formatting and :ref:`fmt/std.h <std-api>` for other standard library
types.
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
template and implement ``parse`` and ``format`` methods::
#include <fmt/core.h>
struct point {
double x, y;
};
template <> struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential.
char presentation = 'f';
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator {
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Please also note that this character range may be empty, in case of
// the "{}" format string, so therefore you should check ctx.begin()
// for equality with ctx.end().
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// Check if reached the end of the range:
if (it != end && *it != '}') ctx.on_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
auto format(const point& p, format_context& ctx) const -> format_context::iterator {
// ctx.out() is an output iterator to write to.
return presentation == 'f'
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
: fmt::format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
}
};
Then you can pass objects of type ``point`` to any formatting function::
point p = {1, 2};
std::string s = fmt::format("{:f}", p);
// s == "(1.0, 2.0)"
You can also reuse existing formatters via inheritance or composition, for
example::
// color.h:
#include <fmt/core.h>
enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view> {
// parse is inherited from formatter<string_view>.
auto format(color c, format_context& ctx) const;
};
// color.cc:
#include "color.h"
#include <fmt/format.h>
auto fmt::formatter<color>::format(color c, format_context& ctx) const {
string_view name = "unknown";
switch (c) {
case color::red: name = "red"; break;
case color::green: name = "green"; break;
case color::blue: name = "blue"; break;
}
return formatter<string_view>::format(name, ctx);
}
Note that ``formatter<string_view>::format`` is defined in ``fmt/format.h`` so
it has to be included in the source file.
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
all string format specifications, for example
.. code-block:: c++
fmt::format("{:>10}", color::blue)
will return ``" blue"``.
You can also write a formatter for a hierarchy of classes::
// demo.h:
#include <type_traits>
#include <fmt/core.h>
struct A {
virtual ~A() {}
virtual std::string name() const { return "A"; }
};
struct B : A {
virtual std::string name() const { return "B"; }
};
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string> {
auto format(const A& a, format_context& ctx) const {
return fmt::formatter<std::string>::format(a.name(), ctx);
}
};
// demo.cc:
#include "demo.h"
#include <fmt/format.h>
int main() {
B b;
A& a = b;
fmt::print("{}", a); // prints "B"
}
If a type provides both a ``formatter`` specialization and an implicit
conversion to a formattable type, the specialization takes precedence over the
conversion.
For enums {fmt} also provides the ``format_as`` extension API. To format an enum
via this API define ``format_as`` that takes this enum and converts it to the
underlying type. ``format_as`` should be defined in the same namespace as the
enum.
Example (https://godbolt.org/z/r7vvGE1v7)::
#include <fmt/format.h>
namespace kevin_namespacy {
enum class film {
house_of_cards, american_beauty, se7en = 7
};
auto format_as(film f) { return fmt::underlying(f); }
}
int main() {
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
}
Named Arguments
---------------
@@ -99,11 +259,11 @@ Argument Lists
--------------
You can create your own formatting function with compile-time checks and small
binary footprint, for example (https://godbolt.org/z/oba4Mc):
binary footprint, for example (https://godbolt.org/z/vajfWEG4b):
.. code:: c++
#include <fmt/format.h>
#include <fmt/core.h>
void vlog(const char* file, int line, fmt::string_view format,
fmt::format_args args) {
@@ -111,13 +271,12 @@ binary footprint, for example (https://godbolt.org/z/oba4Mc):
fmt::vprint(format, args);
}
template <typename S, typename... Args>
void log(const char* file, int line, const S& format, Args&&... args) {
template <typename... T>
void log(const char* file, int line, fmt::format_string<T...> format, T&&... args) {
vlog(file, line, format, fmt::make_format_args(args...));
}
#define MY_LOG(format, ...) \
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
#define MY_LOG(format, ...) log(__FILE__, __LINE__, format, __VA_ARGS__)
MY_LOG("invalid squishiness: {}", 42);
@@ -156,19 +315,6 @@ Compatibility
.. doxygentypedef:: fmt::string_view
Locale
------
All formatting is locale-independent by default. Use the ``'L'`` format
specifier to insert the appropriate number separator characters from the
locale::
#include <fmt/core.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
.. _format-api:
Format API
@@ -177,156 +323,6 @@ Format API
``fmt/format.h`` defines the full format API providing additional formatting
functions and locale support.
.. _udt:
Formatting User-Defined Types
-----------------------------
The {fmt} library provides formatters for many standard C++ types.
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
and time formatting and :ref:`fmt/std.h <std-api>` for path and variant
formatting.
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
template and implement ``parse`` and ``format`` methods::
#include <fmt/format.h>
struct point {
double x, y;
};
template <> struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential.
char presentation = 'f';
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Please also note that this character range may be empty, in case of
// the "{}" format string, so therefore you should check ctx.begin()
// for equality with ctx.end().
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// Check if reached the end of the range:
if (it != end && *it != '}') throw format_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
// ctx.out() is an output iterator to write to.
return presentation == 'f'
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
: fmt::format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
}
};
Then you can pass objects of type ``point`` to any formatting function::
point p = {1, 2};
std::string s = fmt::format("{:f}", p);
// s == "(1.0, 2.0)"
You can also reuse existing formatters via inheritance or composition, for
example::
enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view> {
// parse is inherited from formatter<string_view>.
template <typename FormatContext>
auto format(color c, FormatContext& ctx) const {
string_view name = "unknown";
switch (c) {
case color::red: name = "red"; break;
case color::green: name = "green"; break;
case color::blue: name = "blue"; break;
}
return formatter<string_view>::format(name, ctx);
}
};
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
all string format specifications, for example
.. code-block:: c++
fmt::format("{:>10}", color::blue)
will return ``" blue"``.
You can also write a formatter for a hierarchy of classes::
#include <type_traits>
#include <fmt/format.h>
struct A {
virtual ~A() {}
virtual std::string name() const { return "A"; }
};
struct B : A {
virtual std::string name() const { return "B"; }
};
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string> {
template <typename FormatCtx>
auto format(const A& a, FormatCtx& ctx) const {
return fmt::formatter<std::string>::format(a.name(), ctx);
}
};
int main() {
B b;
A& a = b;
fmt::print("{}", a); // prints "B"
}
If a type provides both a ``formatter`` specialization and an implicit
conversion to a formattable type, the specialization takes precedence over the
conversion.
For enums {fmt} also provides the ``format_as`` extension API. To format an enum
via this API define ``format_as`` that takes this enum and converts it to the
underlying type. ``format_as`` should be defined in the same namespace as the
enum.
Example (https://godbolt.org/z/r7vvGE1v7)::
#include <fmt/format.h>
namespace kevin_namespacy {
enum class film {
house_of_cards, american_beauty, se7en = 7
};
auto format_as(film f) { return fmt::underlying(f); }
}
int main() {
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
}
Literal-Based API
-----------------
@@ -338,7 +334,7 @@ Utilities
---------
.. doxygenfunction:: fmt::ptr(T p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T, Deleter> &p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
.. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type
@@ -404,6 +400,41 @@ normally don't do any allocations for built-in and string types except for
non-default floating-point formatting that occasionally falls back on
``sprintf``.
Locale
------
All formatting is locale-independent by default. Use the ``'L'`` format
specifier to insert the appropriate number separator characters from the
locale::
#include <fmt/core.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
``fmt/format.h`` provides the following overloads of formatting functions that
take ``std::locale`` as a parameter. The locale type is a template parameter to
avoid the expensive ``<locale>`` include.
.. doxygenfunction:: format(const Locale& loc, format_string<T...> fmt, T&&... args) -> std::string
.. doxygenfunction:: format_to(OutputIt out, const Locale& loc, format_string<T...> fmt, T&&... args) -> OutputIt
.. doxygenfunction:: formatted_size(const Locale& loc, format_string<T...> fmt, T&&... args) -> size_t
.. _legacy-checks:
Legacy Compile-Time Format String Checks
----------------------------------------
``FMT_STRING`` enables compile-time checks on older compilers. It requires C++14
or later and is a no-op in C++11.
.. doxygendefine:: FMT_STRING
To force the use of legacy compile-time checks, define the preprocessor variable
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
will fail to compile with regular strings.
.. _ranges-api:
Range and Tuple Formatting
@@ -477,6 +508,7 @@ Standard Library Types Formatting
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_
Formatting Variants
-------------------

View File

@@ -4,7 +4,7 @@
import errno, os, re, sys
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0']
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0']
class Pip:
def __init__(self, venv_dir):
@@ -83,8 +83,7 @@ def build_docs(version='dev', **kwargs):
internal_symbols = [
'fmt::detail::.*',
'basic_data<>',
'fmt::type_identity',
'fmt::dynamic_formatter'
'fmt::type_identity'
]
noisy_warnings = [
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \

View File

@@ -109,8 +109,8 @@ Note that unless a minimum field width is defined, the field width will always
be the same size as the data to fill it, so that the alignment option has no
meaning in this case.
The *sign* option is only valid for number types, and can be one of the
following:
The *sign* option is only valid for floating point and signed integer types,
and can be one of the following:
+---------+------------------------------------------------------------+
| Option | Meaning |
@@ -304,8 +304,8 @@ The available presentation types for pointers are:
Chrono Format Specifications
============================
Format specifications for chrono types and ``std::tm`` have the following
syntax:
Format specifications for chrono duration and time point types as well as
``std::tm`` have the following syntax:
.. productionlist:: sf
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
@@ -321,20 +321,89 @@ syntax:
Literal chars are copied unchanged to the output. Precision is valid only for
``std::chrono::duration`` types with a floating-point representation type.
The available presentation types (*chrono_type*) for chrono durations and time
points are:
The available presentation types (*chrono_type*) are:
+---------+--------------------------------------------------------------------+
| Type | Meaning |
+=========+====================================================================+
| ``'a'`` | The abbreviated weekday name, e.g. "Sat". If the value does not |
| | contain a valid weekday, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'A'`` | The full weekday name, e.g. "Saturday". If the value does not |
| | contain a valid weekday, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'b'`` | The abbreviated month name, e.g. "Nov". If the value does not |
| | contain a valid month, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'B'`` | The full month name, e.g. "November". If the value does not |
| | contain a valid month, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'c'`` | The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". |
| | The modified command ``%Ec`` produces the locale's alternate date |
| | and time representation. |
+---------+--------------------------------------------------------------------+
| ``'C'`` | The year divided by 100 using floored division, e.g. "55". If the |
| | result is a single decimal digit, it is prefixed with 0. |
| | The modified command ``%EC`` produces the locale's alternative |
| | representation of the century. |
+---------+--------------------------------------------------------------------+
| ``'d'`` | The day of month as a decimal number. If the result is a single |
| | decimal digit, it is prefixed with 0. The modified command ``%Od`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'D'`` | Equivalent to ``%m/%d/%y``, e.g. "11/12/55". |
+---------+--------------------------------------------------------------------+
| ``'e'`` | The day of month as a decimal number. If the result is a single |
| | decimal digit, it is prefixed with a space. The modified command |
| | ``%Oe`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'F'`` | Equivalent to ``%Y-%m-%d``, e.g. "1955-11-12". |
+---------+--------------------------------------------------------------------+
| ``'g'`` | The last two decimal digits of the ISO week-based year. If the |
| | result is a single digit it is prefixed by 0. |
+---------+--------------------------------------------------------------------+
| ``'G'`` | The ISO week-based year as a decimal number. If the result is less |
| | than four digits it is left-padded with 0 to four digits. |
+---------+--------------------------------------------------------------------+
| ``'h'`` | Equivalent to ``%b``, e.g. "Nov". |
+---------+--------------------------------------------------------------------+
| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a |
| | single digit, it is prefixed with 0. The modified command ``%OH`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'I'`` | The hour (12-hour clock) as a decimal number. If the result is a |
| | single digit, it is prefixed with 0. The modified command ``%OI`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'j'`` | If the type being formatted is a specialization of duration, the |
| | decimal number of days without padding. Otherwise, the day of the |
| | year as a decimal number. Jan 1 is 001. If the result is less than |
| | three digits, it is left-padded with 0 to three digits. |
+---------+--------------------------------------------------------------------+
| ``'m'`` | The month as a decimal number. Jan is 01. If the result is a |
| | single digit, it is prefixed with 0. The modified command ``%Om`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'M'`` | The minute as a decimal number. If the result is a single digit, |
| | it is prefixed with 0. The modified command ``%OM`` produces the |
| | locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'n'`` | A new-line character. |
+---------+--------------------------------------------------------------------+
| ``'p'`` | The AM/PM designations associated with a 12-hour clock. |
+---------+--------------------------------------------------------------------+
| ``'q'`` | The duration's unit suffix. |
+---------+--------------------------------------------------------------------+
| ``'Q'`` | The duration's numeric value (as if extracted via ``.count()``). |
+---------+--------------------------------------------------------------------+
| ``'r'`` | The 12-hour clock time, e.g. "10:04:00 PM". |
+---------+--------------------------------------------------------------------+
| ``'R'`` | Equivalent to ``%H:%M``, e.g. "22:04". |
+---------+--------------------------------------------------------------------+
| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
| | 10, the result is prefixed with 0. If the precision of the input |
| | cannot be exactly represented with seconds, then the format is a |
@@ -345,9 +414,66 @@ points are:
| | decimal point is localized according to the locale. The modified |
| | command ``%OS`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'t'`` | A horizontal-tab character. |
+---------+--------------------------------------------------------------------+
| ``'T'`` | Equivalent to ``%H:%M:%S``. |
+---------+--------------------------------------------------------------------+
| ``'u'`` | The ISO weekday as a decimal number (1-7), where Monday is 1. The |
| | modified command ``%Ou`` produces the locale's alternative |
| | representation. |
+---------+--------------------------------------------------------------------+
| ``'U'`` | The week number of the year as a decimal number. The first Sunday |
| | of the year is the first day of week 01. Days of the same year |
| | prior to that are in week 00. If the result is a single digit, it |
| | is prefixed with 0. The modified command ``%OU`` produces the |
| | locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'V'`` | The ISO week-based week number as a decimal number. If the result |
| | is a single digit, it is prefixed with 0. The modified command |
| | ``%OV`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'w'`` | The weekday as a decimal number (0-6), where Sunday is 0. |
| | The modified command ``%Ow`` produces the locale's alternative |
| | representation. |
+---------+--------------------------------------------------------------------+
| ``'W'`` | The week number of the year as a decimal number. The first Monday |
| | of the year is the first day of week 01. Days of the same year |
| | prior to that are in week 00. If the result is a single digit, it |
| | is prefixed with 0. The modified command ``%OW`` produces the |
| | locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'x'`` | The date representation, e.g. "11/12/55". The modified command |
| | ``%Ex`` produces the locale's alternate date representation. |
+---------+--------------------------------------------------------------------+
| ``'X'`` | The time representation, e.g. "10:04:00". The modified command |
| | ``%EX`` produces the locale's alternate time representation. |
+---------+--------------------------------------------------------------------+
| ``'y'`` | The last two decimal digits of the year. If the result is a single |
| | digit it is prefixed by 0. The modified command ``%Oy`` produces |
| | the locale's alternative representation. The modified command |
| | ``%Ey`` produces the locale's alternative representation of offset |
| | from ``%EC`` (year only). |
+---------+--------------------------------------------------------------------+
| ``'Y'`` | The year as a decimal number. If the result is less than four |
| | digits it is left-padded with 0 to four digits. The modified |
| | command ``%EY`` produces the locale's alternative full year |
| | representation. |
+---------+--------------------------------------------------------------------+
| ``'z'`` | The offset from UTC in the ISO 8601:2004 format. For example -0430 |
| | refers to 4 hours 30 minutes behind UTC. If the offset is zero, |
| | +0000 is used. The modified commands ``%Ez`` and ``%Oz`` insert a |
| | ``:`` between the hours and minutes: -04:30. If the offset |
| | information is not available, an exception of type |
| | ``format_error`` is thrown. |
+---------+--------------------------------------------------------------------+
| ``'Z'`` | The time zone abbreviation. If the time zone abbreviation is not |
| | available, an exception of type ``format_error`` is thrown. |
+---------+--------------------------------------------------------------------+
| ``'%'`` | A % character. |
+---------+--------------------------------------------------------------------+
Specifiers that have a calendaric component such as ``'d'`` (the day of month)
are valid only for ``std::tm`` and not durations or time points.
are valid only for ``std::tm`` and time points but not durations.
.. range-specs:
@@ -371,7 +497,7 @@ Examples::
fmt::format("{}", std::vector{10, 20, 30});
// Result: [10, 20, 30]
fmt::format("{::#x}", std::vector{10, 20, 30});
// Result: [0xa, 0x14, 0x13]
// Result: [0xa, 0x14, 0x1e]
fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'});
// Result: ['h', 'e', 'l', 'l', 'o']
fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'});

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
@@ -423,26 +423,6 @@ FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
return ansi_color_escape<Char>(em);
}
template <typename Char> inline void fputs(const Char* chars, FILE* stream) {
int result = std::fputs(chars, stream);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
template <> inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) {
int result = std::fputws(chars, stream);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
template <typename Char> inline void reset_color(FILE* stream) {
fputs("\x1b[0m", stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) {
fputs(L"\x1b[0m", stream);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
@@ -479,17 +459,19 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
FMT_END_DETAIL_NAMESPACE
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, detail::to_string_view(format), args);
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
format_args args) {
// Legacy wide streams are not supported.
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
if (detail::is_utf8()) {
detail::print(f, basic_string_view<Char>(buf.begin(), buf.size()));
} else {
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
detail::print(f, string_view(buf.begin(), buf.size()));
return;
}
buf.push_back('\0');
int result = std::fputs(buf.data(), f);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
/**
@@ -566,7 +548,7 @@ OutputIt vformat_to(
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
/**
@@ -645,7 +627,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts)
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

View File

@@ -331,14 +331,14 @@ template <typename T, typename Char> struct parse_specs_result {
int next_arg_id;
};
constexpr int manual_indexing_id = -1;
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {},
next_arg_id);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
@@ -348,22 +348,18 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int operator()() {
constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int operator()(int id) {
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int operator()(basic_string_view<Char> id) {
constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr void on_error(const char* message) {
FMT_THROW(format_error(message));
}
};
template <typename Char> struct parse_arg_id_result {
@@ -501,7 +497,7 @@ constexpr auto compile(S format_str) {
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
@@ -605,7 +601,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
} // namespace literals
#endif
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,9 @@
#define FMT_FORMAT_INL_H_
#include <algorithm>
#include <cctype>
#include <cerrno> // errno
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstring> // std::memmove
#include <cwchar>
#include <exception>
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#endif
FMT_FUNC auto write_loc(appender out, loc_value value,
const format_specs<>& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto locale = loc.get<std::locale>();
// We cannot use the num_put<char> facet because it may produce output in
// a wrong encoding.
using facet = format_facet<std::locale>;
if (std::has_facet<facet>(locale))
return std::use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs);
#endif
return false;
}
} // namespace detail
#if !FMT_MSC_VERSION
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
grouping_ = numpunct.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
}
template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, loc_value val, const format_specs<>& specs) const -> bool {
return val.visit(
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_});
}
#endif
FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str,
FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
format_args args) {
auto ec = std::error_code(error_code, std::generic_category());
return std::system_error(ec, vformat(format_str, args));
return std::system_error(ec, vformat(fmt, args));
}
namespace detail {
@@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
return (n >> r) | (n << (64 - r));
}
// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept {
#if FMT_USE_INT128
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};
#elif defined(_MSC_VER) && defined(_M_X64)
auto result = uint128_fallback();
result.lo_ = _umul128(x, y, &result.hi_);
return result;
#else
const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>());
uint64_t a = x >> 32;
uint64_t b = x & mask;
uint64_t c = y >> 32;
uint64_t d = y & mask;
uint64_t ac = a * c;
uint64_t bc = b * c;
uint64_t ad = a * d;
uint64_t bd = b * d;
uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
(intermediate << 32) + (bd & mask)};
#endif
}
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
namespace dragonbox {
// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept {
#if FMT_USE_INT128
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
return static_cast<uint64_t>(p >> 64);
#elif defined(_MSC_VER) && defined(_M_X64)
return __umulh(x, y);
#else
return umul128(x, y).high();
#endif
}
// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a
// 128-bit unsigned integer.
inline uint128_fallback umul192_upper128(uint64_t x,
uint128_fallback y) noexcept {
uint128_fallback r = umul128(x, y.high());
r += umul128_upper64(x, y.low());
return r;
}
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
// 64-bit unsigned integer.
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
@@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
return x * y;
}
// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from
// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.
inline int floor_log10_pow2(int e) noexcept {
FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent");
static_assert((-1 >> 1) == -1, "right shift is not arithmetic");
return (e * 315653) >> 20;
}
// Various fast log computations.
inline int floor_log2_pow10(int e) noexcept {
FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
return (e * 1741647) >> 19;
}
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
return (e * 631305 - 261663) >> 21;
}
static constexpr struct {
FMT_INLINE_VARIABLE constexpr struct {
uint32_t divisor;
int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
}
// Various subroutines using pow10 cache
template <class T> struct cache_accessor;
template <typename T> struct cache_accessor;
template <> struct cache_accessor<float> {
using carrier_uint = float_info<float>::carrier_uint;
@@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> {
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
{0xc5a05277621be293, 0xc7098b7305241886},
{ 0xf70867153aa2db38,
0xb8cbee4fc66d1ea8 }
{0xf70867153aa2db38, 0xb8cbee4fc66d1ea8},
{0x9a65406d44a5c903, 0x737f74f1dc043329},
{0xc0fe908895cf3b44, 0x505f522e53053ff3},
{0xf13e34aabb430a15, 0x647726b9e7c68ff0},
{0x96c6e0eab509e64d, 0x5eca783430dc19f6},
{0xbc789925624c5fe0, 0xb67d16413d132073},
{0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890},
{0x933e37a534cbaae7, 0x8e91b962f7b6f15a},
{0xb80dc58e81fe95a1, 0x723627bbb5a4adb1},
{0xe61136f2227e3b09, 0xcec3b1aaa30dd91d},
{0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2},
{0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e},
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
{ 0xdb68c2ca82ed2a05,
0xa67398db9f6820e2 }
#else
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
@@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> {
{0x8da471a9de737e24, 0x5ceaecfed289e5d3},
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
{0xb8da1662e7b00a17, 0x3d6a751f3b936244},
{ 0x95527a5202df0ccb,
0x0f37801e0c43ebc9 }
{0x95527a5202df0ccb, 0x0f37801e0c43ebc9},
{0xf13e34aabb430a15, 0x647726b9e7c68ff0}
#endif
};
@@ -1138,8 +1114,12 @@ template <> struct cache_accessor<double> {
}
};
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
return cache_accessor<double>::get_cached_power(k);
}
// Various integer checks
template <class T>
template <typename T>
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
@@ -1150,8 +1130,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
// Remove trailing zeros from n and return the number of zeros removed (float)
FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
// See https://github.com/fmtlib/fmt/issues/3163 for more details.
const uint32_t mod_inv_5 = 0xcccccccd;
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
// Casts are needed to workaround a bug in MSVC 19.22 and older.
const uint32_t mod_inv_25 =
static_cast<uint32_t>(uint64_t(mod_inv_5) * mod_inv_5);
int s = 0;
while (true) {
@@ -1165,7 +1149,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
n = q;
s |= 1;
}
return s;
}
@@ -1223,7 +1206,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
}
// The main algorithm for shorter interval case
template <class T>
template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
decimal_fp<T> ret_value;
// Compute k and beta
@@ -1394,17 +1377,6 @@ small_divisor_case_label:
return ret_value;
}
} // namespace dragonbox
#ifdef _MSC_VER
FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...)
-> int {
auto args = va_list();
va_start(args, fmt);
int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args);
va_end(args);
return result;
}
#endif
} // namespace detail
template <> struct formatter<detail::bigint> {
@@ -1413,9 +1385,8 @@ template <> struct formatter<detail::bigint> {
return ctx.begin();
}
template <typename FormatContext>
auto format(const detail::bigint& n, FormatContext& ctx) const ->
typename FormatContext::iterator {
auto format(const detail::bigint& n, format_context& ctx) const
-> format_context::iterator {
auto out = ctx.out();
bool first = true;
for (auto i = n.bigits_.size(); i > 0; --i) {
@@ -1474,57 +1445,44 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
}
namespace detail {
#ifdef _WIN32
#ifndef _WIN32
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
#else
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
void*, const void*, dword, dword*, void*);
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
auto fd = _fileno(f);
if (_isatty(fd)) {
detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
auto written = detail::dword();
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
u16.c_str(), static_cast<uint32_t>(u16.size()),
&written, nullptr)) {
return true;
}
}
// We return false if the file descriptor was not TTY, or it was but
// SetConsoleW failed which can happen if the output has been redirected to
// NUL. In both cases when we return false, we should attempt to do regular
// write via fwrite or std::ostream::write.
return false;
}
#endif
FMT_FUNC void print(std::FILE* f, string_view text) {
#ifdef _WIN32
if (write_console(f, text)) return;
#endif
detail::fwrite_fully(text.data(), 1, text.size(), f);
}
} // namespace detail
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
memory_buffer buffer;
detail::vformat_to(buffer, format_str, args);
detail::print(f, {buffer.data(), buffer.size()});
if (!_isatty(fd)) return false;
auto u16 = utf8_to_utf16(text);
auto written = dword();
return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
static_cast<uint32_t>(u16.size()), &written, nullptr);
}
#ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding.
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
format_args args) {
memory_buffer buffer;
detail::vformat_to(buffer, format_str,
FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt,
basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f);
}
#endif
FMT_FUNC void vprint(string_view format_str, format_args args) {
vprint(stdout, format_str, args);
FMT_FUNC void print(std::FILE* f, string_view text) {
if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
}
} // namespace detail
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
detail::print(f, {buffer.data(), buffer.size()});
}
FMT_FUNC void vprint(string_view fmt, format_args args) {
vprint(stdout, fmt, args);
}
namespace detail {

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,7 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
/**
\rst
@@ -120,48 +120,10 @@ template <typename Char> class basic_cstring_view {
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(),
basic_format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#ifdef _WIN32
FMT_API const std::error_category& system_category() noexcept;
FMT_BEGIN_DETAIL_NAMESPACE
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
private:
memory_buffer buffer_;
public:
utf16_to_utf8() {}
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
operator string_view() const { return string_view(&buffer_[0], size()); }
size_t size() const { return buffer_.size() - 1; }
const char* c_str() const { return &buffer_[0]; }
std::string str() const { return std::string(&buffer_[0], size()); }
// Performs conversion returning a system error code instead of
// throwing exception on conversion error. This method may still throw
// in case of memory allocation error.
FMT_API int convert(basic_string_view<wchar_t> s);
};
FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
FMT_END_DETAIL_NAMESPACE
@@ -355,6 +317,12 @@ class FMT_API file {
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
buffered_file fdopen(const char* mode);
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
};
// Returns the memory page size.
@@ -397,6 +365,28 @@ struct ostream_params {
# endif
};
class file_buffer final : public buffer<char> {
file file_;
FMT_API void grow(size_t) override;
public:
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other);
FMT_API ~file_buffer();
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
FMT_END_DETAIL_NAMESPACE
// Added {} below to work around default constructor error known to
@@ -404,49 +394,32 @@ FMT_END_DETAIL_NAMESPACE
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> {
class FMT_API ostream {
private:
file file_;
void grow(size_t) override;
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
: buffer_(path, params) {}
public:
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() {
flush();
file_.close();
}
void close() { buffer_.close(); }
/**
Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(*this), fmt,
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
fmt::make_format_args(args...));
}
};
@@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) {
}
#endif // FMT_USE_FCNTL
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_OS_H_

View File

@@ -8,8 +8,8 @@
#ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_
#include <fstream>
#include <ostream>
#include <fstream> // std::filebuf
#if defined(_WIN32) && defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
@@ -21,48 +21,14 @@
FMT_BEGIN_NAMESPACE
template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail {
// Checks if T has a user-defined operator<<.
template <typename T, typename Char, typename Enable = void>
class is_streamable {
private:
template <typename U>
static auto test(int)
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
<< std::declval<U>()) != 0>;
template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0));
public:
is_streamable() = default;
static const bool value = result::value;
};
// Formatting of built-in types and arrays is intentionally disabled because
// it's handled by standard (non-ostream) formatters.
template <typename T, typename Char>
struct is_streamable<
T, Char,
enable_if_t<
std::is_arithmetic<T>::value || std::is_array<T>::value ||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
std::is_same<T, std_string_view<Char>>::value ||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
: std::false_type {};
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
template <class Tag, class BufType, FILE* BufType::*FileMemberPtr>
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
@@ -84,8 +50,8 @@ inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
FILE* c_file;
if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
else
@@ -145,7 +111,7 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale());
detail::format_value(buffer, value, ctx.locale());
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
@@ -180,13 +146,6 @@ auto streamed(const T& value) -> detail::streamed_view<T> {
namespace detail {
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: basic_ostream_formatter<Char> {
using basic_ostream_formatter<Char>::format;
};
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
@@ -232,6 +191,19 @@ void print(std::wostream& os,
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
}
FMT_MODULE_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_MODULE_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
}
FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_

View File

@@ -14,7 +14,7 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter { printf_formatter() = delete; };
@@ -81,13 +81,13 @@ class printf_precision_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
int operator()(T value) {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
FMT_THROW(format_error("number is too big"));
throw_format_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
int operator()(T) {
FMT_THROW(format_error("precision is not integer"));
throw_format_error("precision is not integer");
return 0;
}
};
@@ -194,12 +194,10 @@ template <typename Char> struct get_cstring {
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
private:
using format_specs = basic_format_specs<Char>;
format_specs& specs_;
format_specs<Char>& specs_;
public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
@@ -209,24 +207,31 @@ template <typename Char> class printf_width_handler {
width = 0 - width;
}
unsigned int_max = max_value<int>();
if (width > int_max) FMT_THROW(format_error("number is too big"));
if (width > int_max) throw_format_error("number is too big");
return static_cast<unsigned>(width);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
unsigned operator()(T) {
FMT_THROW(format_error("width is not integer"));
throw_format_error("width is not integer");
return 0;
}
};
// Workaround for a bug with the XL compiler when initializing
// printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The ``printf`` argument formatter.
template <typename OutputIt, typename Char>
class printf_arg_formatter : public arg_formatter<Char> {
private:
using base = arg_formatter<Char>;
using context_type = basic_printf_context<OutputIt, Char>;
using format_specs = basic_format_specs<Char>;
context_type& context_;
@@ -237,8 +242,8 @@ class printf_arg_formatter : public arg_formatter<Char> {
}
public:
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
: base{iter, s, locale_ref()}, context_(ctx) {}
printf_arg_formatter(OutputIt iter, format_specs<Char>& s, context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); }
@@ -247,7 +252,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs;
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
@@ -300,8 +305,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
};
template <typename Char>
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
const Char* end) {
void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
@@ -328,8 +332,8 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
}
template <typename Char, typename GetArg>
int parse_header(const Char*& it, const Char* end,
basic_format_specs<Char>& specs, GetArg get_arg) {
int parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
GetArg get_arg) {
int arg_index = -1;
Char c = *it;
if (c >= '0' && c <= '9') {
@@ -344,7 +348,7 @@ int parse_header(const Char*& it, const Char* end,
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
if (value == -1) FMT_THROW(format_error("number is too big"));
if (value == -1) throw_format_error("number is too big");
specs.width = value;
return arg_index;
}
@@ -355,7 +359,7 @@ int parse_header(const Char*& it, const Char* end,
if (it != end) {
if (*it >= '0' && *it <= '9') {
specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
if (specs.width == -1) throw_format_error("number is too big");
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
@@ -365,12 +369,52 @@ int parse_header(const Char*& it, const Char* end,
return arg_index;
}
inline auto parse_printf_presentation_type(char c, type t)
-> presentation_type {
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'x':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none;
case 'a':
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
case 'A':
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none;
case 'f':
return in(t, float_set) ? pt::fixed_lower : pt::none;
case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none;
case 'g':
return in(t, float_set) ? pt::general_lower : pt::none;
case 'G':
return in(t, float_set) ? pt::general_upper : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
}
}
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using OutputIt = buffer_appender<Char>;
auto out = OutputIt(buf);
auto context = basic_printf_context<OutputIt, Char>(out, args);
using iterator = buffer_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<iterator, Char>(out, args);
auto parse_ctx = basic_printf_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
@@ -387,26 +431,25 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
const Char* end = parse_ctx.end();
auto it = start;
while (it != end) {
if (!detail::find<false, Char>(it, end, '%', it)) {
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
if (!find<false, Char>(it, end, '%', it)) {
it = end; // find leaves it == nullptr if it doesn't find '%'.
break;
}
Char c = *it++;
if (it != end && *it == c) {
out = detail::write(
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
out = write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
start = ++it;
continue;
}
out = detail::write(out, basic_string_view<Char>(
start, detail::to_unsigned(it - 1 - start)));
out =
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
basic_format_specs<Char> specs;
auto specs = format_specs<Char>();
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) parse_ctx.on_error("argument not found");
if (arg_index == 0) throw_format_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
@@ -417,7 +460,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
visit_format_arg(printf_precision_handler(), get_arg(-1)));
} else {
specs.precision = 0;
}
@@ -429,17 +472,15 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
if (specs.precision >= 0 && arg.is_integral())
specs.fill[0] =
' '; // Ignore '0' flag for non-numeric types or if '-' present.
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
arg = make_arg<basic_printf_context<iterator, Char>>(
basic_string_view<Char>(
str, detail::to_unsigned(nul != str_end ? nul - str
: specs.precision)));
str, to_unsigned(nul != str_end ? nul - str : specs.precision)));
}
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
specs.alt = false;
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
@@ -451,7 +492,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
Char t = it != end ? *it : 0;
using detail::convert_arg;
switch (c) {
case 'h':
if (t == 'h') {
@@ -490,7 +530,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
}
// Parse type.
if (it == end) FMT_THROW(format_error("invalid format string"));
if (it == end) throw_format_error("invalid format string");
char type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
@@ -501,22 +541,21 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
break;
case 'c':
visit_format_arg(
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
arg);
char_converter<basic_printf_context<iterator, Char>>(arg), arg);
break;
}
}
specs.type = parse_presentation_type(type);
specs.type = parse_printf_presentation_type(type, arg.type());
if (specs.type == presentation_type::none)
parse_ctx.on_error("invalid type specifier");
throw_format_error("invalid format specifier");
start = it;
// Format argument.
out = visit_format_arg(
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
printf_arg_formatter<iterator, Char>(out, specs, context), arg);
}
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
FMT_END_DETAIL_NAMESPACE
@@ -559,9 +598,9 @@ inline auto vsprintf(
const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buffer;
vprintf(buffer, detail::to_string_view(fmt), args);
return to_string(buffer);
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, detail::to_string_view(fmt), args);
return to_string(buf);
}
/**
@@ -586,10 +625,10 @@ inline auto vfprintf(
std::FILE* f, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer;
vprintf(buffer, detail::to_string_view(fmt), args);
size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, detail::to_string_view(fmt), args);
size_t size = buf.size();
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
}
@@ -634,7 +673,7 @@ inline auto printf(const S& fmt, const T&... args) -> int {
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_

View File

@@ -22,27 +22,25 @@ FMT_BEGIN_NAMESPACE
namespace detail {
template <typename RangeT, typename OutputIterator>
OutputIterator copy(const RangeT& range, OutputIterator out) {
template <typename Range, typename OutputIt>
auto copy(const Range& range, OutputIt out) -> OutputIt {
for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it;
return out;
}
template <typename OutputIterator>
OutputIterator copy(const char* str, OutputIterator out) {
template <typename OutputIt>
auto copy(const char* str, OutputIt out) -> OutputIt {
while (*str) *out++ = *str++;
return out;
}
template <typename OutputIterator>
OutputIterator copy(char ch, OutputIterator out) {
template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
template <typename OutputIterator>
OutputIterator copy(wchar_t ch, OutputIterator out) {
template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
@@ -69,7 +67,7 @@ template <typename T> class is_map {
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_MAP_AS_LIST
#ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
@@ -82,7 +80,7 @@ template <typename T> class is_set {
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_SET_AS_LIST
#ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
@@ -157,8 +155,9 @@ template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_end(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {};
// the extra int here is because older versions of MSVC don't
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T>
struct is_range_<T, void>
@@ -211,41 +210,61 @@ class is_tuple_formattable_ {
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... I>
static std::true_type check2(index_sequence<I...>,
integer_sequence<bool, (I == I)...>);
template <std::size_t... Is>
static std::true_type check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>);
static std::false_type check2(...);
template <std::size_t... I>
template <std::size_t... Is>
static decltype(check2(
index_sequence<I...>{},
index_sequence<Is...>{},
integer_sequence<
bool, (is_formattable<typename std::tuple_element<I, T>::type,
C>::value)...>{})) check(index_sequence<I...>);
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{})) check(index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
template <class Tuple, class F, size_t... Is>
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept {
template <typename Tuple, typename F, size_t... Is>
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get;
// using free function get<I>(T) now.
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
(void)_; // blocks warnings
// Using a free function get<Is>(Tuple) now.
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
ignore_unused(unused);
}
template <class T>
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
T const&) {
return {};
template <typename Tuple, typename F>
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
std::forward<Tuple>(t), std::forward<F>(f));
}
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 Tuple1, typename Tuple2, typename F, size_t... Is>
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
}
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl {
@@ -269,45 +288,37 @@ using range_reference_type =
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Range>
using uncvref_first_type =
remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>;
template <typename Range>
using uncvref_second_type = remove_cvref_t<
decltype(std::declval<range_reference_type<Range>>().second)>;
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
*out++ = ',';
*out++ = ' ';
return out;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename Char, typename OutputIt>
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
return write_escaped_string(out, str);
}
// These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
auto sv = std_string_view<Char>(str);
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
}
template <typename T>
void operator()(const formatter<T, char_type>& f, const T& v) {
if (i > 0)
ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
}
template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) {
return write_escaped_char(out, v);
}
template <
typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
!std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg& v) {
return write<Char>(out, v);
}
int i;
FormatContext& ctx;
basic_string_view<char_type> separator;
};
} // namespace detail
@@ -321,29 +332,20 @@ template <typename T, typename C> struct is_tuple_formattable {
detail::is_tuple_formattable_<T, C>::value;
};
template <typename TupleT, typename Char>
struct formatter<TupleT, Char,
enable_if_t<fmt::is_tuple_like<TupleT>::value &&
fmt::is_tuple_formattable<TupleT, Char>::value>> {
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
fmt::is_tuple_formattable<Tuple, Char>::value>> {
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{};
// C++11 generic lambda for format().
template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::copy_str<Char>(separator, out);
out = detail::write_range_entry<Char>(out, v);
++i;
}
int i;
typename FormatContext::iterator& out;
basic_string_view<Char> separator;
};
public:
FMT_CONSTEXPR formatter() {}
@@ -359,17 +361,21 @@ struct formatter<TupleT, Char,
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
auto it = ctx.begin();
if (it != ctx.end() && *it != '}')
FMT_THROW(format_error("invalid format specifier"));
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) const
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::copy_str<Char>(opening_bracket_, out);
detail::for_each(values, format_each<FormatContext>{0, out, separator_});
out = detail::copy_str<Char>(closing_bracket_, out);
return out;
ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
formatters_, value,
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return detail::copy_str<Char>(closing_bracket_, ctx.out());
}
};
@@ -398,12 +404,10 @@ template <typename Context> struct range_mapper {
};
template <typename Char, typename Element>
using range_formatter_type = conditional_t<
is_formattable<Element, Char>::value,
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
std::declval<Element>()))>,
Char>,
fallback_formatter<Element, Char>>;
Char>;
template <typename R>
using maybe_const_range =
@@ -413,11 +417,8 @@ using maybe_const_range =
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: disjunction<
is_formattable<uncvref_type<maybe_const_range<R>>, Char>,
has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {};
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename T, typename Char, typename Enable = void>
@@ -426,32 +427,16 @@ struct range_formatter;
template <typename T, typename Char>
struct range_formatter<
T, Char,
enable_if_t<conjunction<
std::is_same<T, remove_cvref_t<T>>,
disjunction<is_formattable<T, Char>,
detail::has_fallback_formatter<T, Char>>>::value>> {
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
is_formattable<T, Char>>::value>> {
private:
detail::range_formatter_type<Char, T> underlying_;
bool custom_specs_ = false;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '['>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, int)
-> decltype(u.set_debug_format()) {
u.set_debug_format();
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
FMT_CONSTEXPR void maybe_set_debug_format() {
maybe_set_debug_format(underlying_, 0);
}
public:
FMT_CONSTEXPR range_formatter() {}
@@ -473,31 +458,24 @@ struct range_formatter<
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') {
maybe_set_debug_format();
return it;
}
if (*it == 'n') {
if (it != end && *it == 'n') {
set_brackets({}, {});
++it;
}
if (*it == '}') {
maybe_set_debug_format();
return it;
if (it != end && *it != '}') {
if (*it != ':') FMT_THROW(format_error("invalid format specifier"));
++it;
} else {
detail::maybe_set_debug_format(underlying_, true);
}
if (*it != ':')
FMT_THROW(format_error("no other top-level range formatters supported"));
custom_specs_ = true;
++it;
ctx.advance_to(it);
return underlying_.parse(ctx);
}
template <typename R, class FormatContext>
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
detail::range_mapper<buffer_context<Char>> mapper;
auto out = ctx.out();
@@ -507,7 +485,6 @@ struct range_formatter<
auto end = detail::range_end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::copy_str<Char>(separator_, out);
;
ctx.advance_to(out);
out = underlying_.format(mapper.map(*it), ctx);
++i;
@@ -520,13 +497,14 @@ struct range_formatter<
enum class range_format { disabled, map, set, sequence, string, debug_string };
namespace detail {
template <typename T> struct range_format_kind_ {
static constexpr auto value = std::is_same<range_reference_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence;
};
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
std::is_same<uncvref_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence> {};
template <range_format K, typename R, typename Char, typename Enable = void>
struct range_default_formatter;
@@ -601,9 +579,6 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
: tuple(t), sep{s} {}
};
template <typename Char, typename... T>
using tuple_arg_join = tuple_join_view<Char, T...>;
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
@@ -673,7 +648,42 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
}
};
FMT_MODULE_EXPORT_BEGIN
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<detail::is_container_adaptor_like<T>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMT_BEGIN_EXPORT
/**
\rst
@@ -716,7 +726,7 @@ auto join(std::initializer_list<T> list, string_view sep)
return join(std::begin(list), std::end(list), sep);
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_RANGES_H_

View File

@@ -8,8 +8,12 @@
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include <cstdlib>
#include <exception>
#include <memory>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include "ostream.h"
@@ -25,6 +29,19 @@
# if FMT_HAS_INCLUDE(<variant>)
# include <variant>
# endif
# if FMT_HAS_INCLUDE(<optional>)
# include <optional>
# endif
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif
#ifdef __cpp_lib_filesystem
@@ -39,12 +56,13 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
}
# ifdef _WIN32
template <>
inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted,
inline void write_escaped_path<char>(memory_buffer& quoted,
const std::filesystem::path& p) {
auto s = p.u8string();
write_escaped_string<char>(
std::back_inserter(quoted),
string_view(reinterpret_cast<const char*>(s.c_str()), s.size()));
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
// Convert UTF-16 to UTF-8.
if (!unicode_to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}))
FMT_THROW(std::runtime_error("invalid utf16"));
}
# endif
template <>
@@ -57,13 +75,19 @@ inline void write_escaped_path<std::filesystem::path::value_type>(
} // namespace detail
FMT_MODULE_EXPORT
template <typename Char>
struct formatter<std::filesystem::path, Char>
: formatter<basic_string_view<Char>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto out = formatter<basic_string_view<Char>>::parse(ctx);
this->set_debug_format(false);
return out;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
typename FormatContext::iterator {
basic_memory_buffer<Char> quoted;
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p);
return formatter<basic_string_view<Char>>::format(
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
@@ -73,12 +97,58 @@ FMT_END_NAMESPACE
#endif
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(std::optional<T> const& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, optional);
ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#ifdef __cpp_lib_variant
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
@@ -100,19 +170,16 @@ template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
// variant_size and variant_alternative check.
template <typename T, typename U = void>
struct is_variant_like_ : std::false_type {};
template <typename T>
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
: std::true_type {};
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... I>
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<I, T>, C>...>
check(std::index_sequence<I...>);
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
@@ -130,7 +197,6 @@ auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt {
}
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
@@ -140,6 +206,7 @@ template <typename T, typename C> struct is_variant_formattable {
detail::is_variant_formattable_<T, C>::value;
};
FMT_MODULE_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
@@ -156,16 +223,127 @@ struct formatter<
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
try {
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
} catch (const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_variant
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
FMT_MODULE_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = true;
}
return it;
}
template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);
const std::type_info& ti = typeid(ex);
#ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
out = detail::write_bytes(out, demangled_name_view, spec);
#elif FMT_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
#else
out = detail::write_bytes(out, string_view(ti.name()), spec);
#endif
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, Char(' '));
out = detail::write_bytes(out, string_view(ex.what()), spec);
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

View File

@@ -12,13 +12,32 @@
#include "format.h"
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
}
FMT_MODULE_EXPORT_BEGIN
inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
loc_value value, const format_specs<wchar_t>& specs,
locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
@@ -33,7 +52,9 @@ inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> basic_runtime<wchar_t> { return {{s}}; }
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
@@ -126,7 +147,7 @@ auto vformat_to(OutputIt out, const S& format_str,
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... Args,
@@ -149,7 +170,7 @@ inline auto vformat_to(
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
template <
@@ -160,7 +181,7 @@ template <
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, to_string_view(format_str),
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
@@ -217,13 +238,22 @@ template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename... T>
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/**
Converts *value* to ``std::wstring`` using the default format for type *T*.
*/
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_XCHAR_H_

View File

@@ -1,49 +1,44 @@
module;
#ifndef __cpp_modules
# error Module not supported.
#endif
// put all implementation-provided headers into the global module fragment
// to prevent attachment to this module
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
# define _CRT_SECURE_NO_WARNINGS
#endif
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
#endif
// Put all implementation-provided headers into the global module fragment
// to prevent attachment to this module.
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <chrono>
#include <climits>
#include <clocale>
#include <cmath>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cwchar>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iterator>
#include <limits>
#include <locale>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <variant>
#include <vector>
#include <version>
#if _MSC_VER
#if __has_include(<cxxabi.h>)
# include <cxxabi.h>
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <intrin.h>
#endif
#if defined __APPLE__ || defined(__FreeBSD__)
@@ -65,22 +60,38 @@ module;
# endif
#endif
#ifdef _WIN32
# if defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# elif defined(_LIBCPP_VERSION)
# include <__std_stream>
# endif
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
export module fmt;
#define FMT_MODULE_EXPORT export
#define FMT_MODULE_EXPORT_BEGIN export {
#define FMT_MODULE_EXPORT_END }
#define FMT_BEGIN_EXPORT export {
#define FMT_END_EXPORT }
#define FMT_BEGIN_DETAIL_NAMESPACE \
} \
namespace detail {
#define FMT_END_DETAIL_NAMESPACE \
} \
export {
// all library-provided declarations and definitions
// must be in the module purview to be exported
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
// - all declarations are detached from module 'fmt'
// - the module behaves like a traditional static library, too
// - all library symbols are mangled traditionally
// - you can mix TUs with either importing or #including the {fmt} API
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
// All library-provided declarations and definitions must be in the module
// purview to be exported.
#include "fmt/args.h"
#include "fmt/chrono.h"
#include "fmt/color.h"
@@ -88,11 +99,17 @@ export module fmt;
#include "fmt/format.h"
#include "fmt/os.h"
#include "fmt/printf.h"
#include "fmt/std.h"
#include "fmt/xchar.h"
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif
// gcc doesn't yet implement private module fragments
#if !FMT_GCC_VERSION
module : private;
module :private;
#endif
#include "format.cc"

View File

@@ -28,12 +28,8 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char;
template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED!
// There is no correspondent extern template in format.h because of
// incompatibility between clang and gcc (#2377).
template FMT_API void vformat_to(buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>,
locale_ref);
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t.

149
src/os.cc
View File

@@ -72,34 +72,6 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE
#ifdef _WIN32
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
if (s_size == 0) {
// WideCharToMultiByte does not support zero length, handle separately.
buffer_.resize(1);
buffer_[0] = 0;
return 0;
}
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
nullptr, nullptr);
if (length == 0) return GetLastError();
buffer_.resize(length + 1);
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
length, nullptr, nullptr);
if (length == 0) return GetLastError();
buffer_[length] = 0;
return 0;
}
namespace detail {
class system_message {
@@ -140,8 +112,8 @@ class utf8_system_category final : public std::error_category {
std::string message(int error_code) const override {
system_message msg(error_code);
if (msg) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
unicode_to_utf8<wchar_t> utf8_message;
if (utf8_message.convert(msg)) {
return utf8_message.str();
}
}
@@ -167,9 +139,10 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
FMT_TRY {
system_message msg(error_code);
if (msg) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
fmt::format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
unicode_to_utf8<wchar_t> utf8_message;
if (utf8_message.convert(msg)) {
fmt::format_to(buffer_appender<char>(out), FMT_STRING("{}: {}"),
message, string_view(utf8_message));
return;
}
}
@@ -192,37 +165,47 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
nullptr);
if (!file_)
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
filename.c_str()));
}
void buffered_file::close() {
if (!file_) return;
int result = FMT_SYSTEM(fclose(file_));
file_ = nullptr;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
int buffered_file::descriptor() const {
#ifdef fileno // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL.
int fd = fileno(file_);
#else
int fd = FMT_POSIX_CALL(fileno(file_));
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
#endif
if (fd == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
return fd;
}
#if FMT_USE_FCNTL
file::file(cstring_view path, int oflag) {
# ifdef _WIN32
using mode_t = int;
using mode_t = int;
# endif
constexpr mode_t mode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
constexpr mode_t default_open_mode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
file::file(cstring_view path, int oflag) {
# if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1;
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
*this = file::open_windows_file(converted.c_str(), oflag);
# else
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
# endif
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
if (fd_ == -1)
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
FMT_THROW(
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
# endif
}
file::~file() noexcept {
@@ -238,7 +221,8 @@ void file::close() {
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
int result = FMT_POSIX_CALL(close(fd_));
fd_ = -1;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
long long file::size() const {
@@ -260,7 +244,7 @@ long long file::size() const {
using Stat = struct stat;
Stat file_stat = Stat();
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
FMT_THROW(system_error(errno, "cannot get file attributes"));
FMT_THROW(system_error(errno, FMT_STRING("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;
@@ -270,14 +254,16 @@ long long file::size() const {
std::size_t file::read(void* buffer, std::size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
return detail::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
return detail::to_unsigned(result);
}
@@ -286,7 +272,8 @@ file file::dup(int fd) {
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd));
if (new_fd == -1)
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
FMT_THROW(system_error(
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
return file(new_fd);
}
@@ -294,8 +281,9 @@ void file::dup2(int fd) {
int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) {
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
fd_, fd));
FMT_THROW(system_error(
errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
fd));
}
}
@@ -320,7 +308,8 @@ void file::pipe(file& read_end, file& write_end) {
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
int result = FMT_POSIX_CALL(pipe(fds));
# endif
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
// The following assignments don't throw because read_fd and write_fd
// are closed.
read_end = file(fds[0]);
@@ -334,28 +323,68 @@ buffered_file file::fdopen(const char* mode) {
# else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
if (!f) {
FMT_THROW(system_error(
errno, FMT_STRING("cannot associate stream with file descriptor")));
}
buffered_file bf(f);
fd_ = -1;
return bf;
}
# if defined(_WIN32) && !defined(__MINGW32__)
file file::open_windows_file(wcstring_view path, int oflag) {
int fd = -1;
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
if (fd == -1) {
FMT_THROW(
system_error(err, FMT_STRING("cannot open file {}"),
detail::unicode_to_utf8<wchar_t>(path.c_str()).c_str()));
}
return file(fd);
}
# endif
# if !defined(__MSDOS__)
long getpagesize() {
# ifdef _WIN32
# ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwPageSize;
# else
# else
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
if (size < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
return size;
# endif
# endif
}
# endif
FMT_API void ostream::grow(size_t) {
namespace detail {
void file_buffer::grow(size_t) {
if (this->size() == this->capacity()) flush();
}
file_buffer::file_buffer(cstring_view path,
const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
file_buffer::file_buffer(file_buffer&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
file_buffer::~file_buffer() {
flush();
delete[] data();
}
} // namespace detail
ostream::~ostream() = default;
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

6
support/Vagrantfile vendored
View File

@@ -13,8 +13,8 @@ Vagrant.configure("2") do |config|
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y g++ make wget git
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz
tar xzf cmake-3.26.0-Linux-x86_64.tar.gz
ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
SHELL
end

View File

@@ -1 +0,0 @@
build --symlink_prefix=/ # Out of source build

View File

@@ -1 +1 @@
5.1.1
6.1.2

View File

@@ -11,8 +11,8 @@ cc_library(
"include/fmt/color.h",
"include/fmt/compile.h",
"include/fmt/core.h",
"include/fmt/format-inl.h",
"include/fmt/format.h",
"include/fmt/format-inl.h",
"include/fmt/os.h",
"include/fmt/ostream.h",
"include/fmt/printf.h",
@@ -21,7 +21,7 @@ cc_library(
"include/fmt/xchar.h",
],
includes = [
"include",
"include",
],
strip_include_prefix = "include",
visibility = ["//visibility:public"],

View File

@@ -1,6 +1,6 @@
# Bazel support
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
## Using {fmt} as a dependency
@@ -37,7 +37,6 @@ git_repository(
branch = "master",
remote = "https://github.com/fmtlib/fmt",
patch_cmds = [
"mv support/bazel/.bazelrc .bazelrc",
"mv support/bazel/.bazelversion .bazelversion",
"mv support/bazel/BUILD.bazel BUILD.bazel",
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
@@ -47,7 +46,6 @@ git_repository(
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
# Even if MSYS2 is installed the Windows related patch commands can still be used.
patch_cmds_win = [
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
@@ -55,7 +53,7 @@ git_repository(
)
```
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
*BUILD.bazel*:
@@ -71,3 +69,6 @@ The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}.
To execute the binary you can run `bazel run //:Demo`.
# Using Bzlmod
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) also provides support for {fmt}.

View File

@@ -11,7 +11,7 @@ buildscript {
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
//
// Notice that 4.0.0 here is the version of [Android Gradle Plugin]
// Accroding to URL above you will need Gradle 6.1 or higher
// According to URL above you will need Gradle 6.1 or higher
//
classpath "com.android.tools.build:gradle:4.1.1"
}

View File

@@ -1,54 +0,0 @@
# C++14 feature support detection
include(CheckCXXCompilerFlag)
function (fmt_check_cxx_compiler_flag flag result)
if (NOT MSVC)
check_cxx_compiler_flag("${flag}" ${result})
endif ()
endfunction ()
if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
if (CMAKE_CXX_STANDARD EQUAL 20)
fmt_check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
fmt_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)
fmt_check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
fmt_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)
fmt_check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
fmt_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)
fmt_check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
fmt_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 ()

View File

@@ -1,5 +1,7 @@
add_subdirectory(gtest)
include(CheckSymbolExists)
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
add_library(test-main STATIC ${TEST_MAIN_SRC})
target_include_directories(test-main PUBLIC
@@ -35,7 +37,7 @@ function(add_fmt_test name)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
endif ()
elseif (ADD_FMT_TEST_MODULE)
set(libs gtest test-module)
set(libs test-main test-module)
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
else ()
set(libs test-main fmt)
@@ -53,6 +55,10 @@ function(add_fmt_test name)
add_test(NAME ${name} COMMAND ${name})
endfunction()
if (FMT_MODULE)
return ()
endif ()
add_fmt_test(args-test)
add_fmt_test(assert-test)
add_fmt_test(chrono-test)
@@ -75,7 +81,13 @@ if (MSVC)
endif()
add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(scan-test)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
if (HAVE_STRPTIME)
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
endif ()
add_fmt_test(std-test)
try_compile(compile_result_unused
${CMAKE_CURRENT_BINARY_DIR}
@@ -94,13 +106,13 @@ add_fmt_test(enforce-checks-test)
target_compile_definitions(enforce-checks-test PRIVATE
-DFMT_ENFORCE_COMPILE_STRING)
if (FMT_CAN_MODULE)
if (FMT_MODULE)
# The tests need {fmt} to be compiled as traditional library
# because of visibility of implementation details.
# If module support is present the module tests require a
# test-only module to be built from {fmt}
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES})
target_compile_features(test-module PUBLIC cxx_std_11)
target_include_directories(test-module PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
enable_module(test-module)

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.25)
project(fmt-test CXX)

View File

@@ -44,7 +44,7 @@ template <> struct formatter<custom_type> {
template <typename FormatContext>
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
return format_to(ctx.out(), "cust={}", p.i);
return fmt::format_to(ctx.out(), "cust={}", p.i);
}
};
FMT_END_NAMESPACE

View File

@@ -7,6 +7,7 @@
#include "fmt/chrono.h"
#include <algorithm>
#include <ctime>
#include <vector>
@@ -14,9 +15,15 @@
#include "util.h" // get_locale
using fmt::runtime;
using testing::Contains;
#if defined(__MINGW32__) && !defined(_UCRT)
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
# define FMT_HAS_C99_STRFTIME 0
#else
# define FMT_HAS_C99_STRFTIME 1
#endif
auto make_tm() -> std::tm {
auto time = std::tm();
time.tm_mday = 1;
@@ -122,7 +129,7 @@ TEST(chrono_test, format_tm) {
make_tm(2000, 1, 3, 12, 14, 16) // W1
};
#if defined(__MINGW32__) && !defined(_UCRT)
#if !FMT_HAS_C99_STRFTIME
GTEST_SKIP() << "Skip the rest of this test because it relies on strftime() "
"conforming to C99, but on this platform, MINGW + MSVCRT, "
"the function conforms only to C89.";
@@ -234,32 +241,135 @@ auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
lhs.tm_isdst == rhs.tm_isdst;
}
TEST(chrono_test, localtime) {
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
}
TEST(chrono_test, gmtime) {
auto t = std::time(nullptr);
auto tm = *std::gmtime(&t);
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
}
template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string {
template <typename TimePoint>
auto strftime_full_utc(TimePoint tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(tp);
auto tm = *std::gmtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
}
TEST(chrono_test, system_clock_time_point) {
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
using time_point =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(42));
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p"};
#ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d",
"%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"};
#endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
// Timezone formatters tests makes sense for localtime.
#if FMT_HAS_C99_STRFTIME
spec_list = {"%z", "%Z"};
#else
spec_list = {"%Z"};
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
if (spec == "%z") {
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
}
}
// Separate tests for UTC, since std::time_put can use local time and ignoring
// the timezone in std::tm (if it presents on platform).
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
std::vector<std::string> tz_names = {"GMT", "UTC"};
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
}
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
EXPECT_EQ("+0000", fmt::format("{:%z}", t1));
EXPECT_EQ("+0000", fmt::format("{:%z}", tm));
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", t1));
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm));
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1));
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm));
}
}
#if FMT_USE_LOCAL_TIME
TEST(chrono_test, localtime) {
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
}
template <typename Duration>
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(tp));
auto tm = *std::localtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
}
TEST(chrono_test, time_point) {
auto t1 = std::chrono::system_clock::now();
EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1));
using time_point =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(42));
EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
TEST(chrono_test, local_system_clock_time_point) {
# ifdef _WIN32
return; // Not supported on Windows.
# endif
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
using time_point = std::chrono::local_time<std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(86400 + 42));
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
@@ -268,19 +378,20 @@ TEST(chrono_test, time_point) {
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
#ifndef _WIN32
# ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
#elif defined(__MINGW32__) && !defined(_UCRT)
# elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
#endif
# endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
@@ -289,8 +400,26 @@ TEST(chrono_test, time_point) {
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
spec_list.cend()) {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime("%z", &tm);
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
}
}
#endif // FMT_USE_LOCAL_TIME
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
TEST(chrono_test, format_default) {
@@ -335,7 +464,7 @@ TEST(chrono_test, format_default) {
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
}
TEST(chrono_test, align) {
TEST(chrono_test, duration_align) {
auto s = std::chrono::seconds(42);
EXPECT_EQ("42s ", fmt::format("{:5}", s));
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
@@ -351,6 +480,35 @@ TEST(chrono_test, align) {
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
}
TEST(chrono_test, tm_align) {
auto t = make_tm(1975, 12, 29, 12, 14, 16);
EXPECT_EQ("1975-12-29 12:14:16", fmt::format("{:%F %T}", t));
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:30%F %T}", t));
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:{}%F %T}", t, 30));
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:<30%F %T}", t));
EXPECT_EQ(" 1975-12-29 12:14:16 ", fmt::format("{:^30%F %T}", t));
EXPECT_EQ(" 1975-12-29 12:14:16", fmt::format("{:>30%F %T}", t));
EXPECT_EQ("1975-12-29 12:14:16***********", fmt::format("{:*<30%F %T}", t));
EXPECT_EQ("*****1975-12-29 12:14:16******", fmt::format("{:*^30%F %T}", t));
EXPECT_EQ("***********1975-12-29 12:14:16", fmt::format("{:*>30%F %T}", t));
}
TEST(chrono_test, tp_align) {
auto tp = std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::system_clock::from_time_t(0));
EXPECT_EQ("00:00.000000", fmt::format("{:%M:%S}", tp));
EXPECT_EQ("00:00.000000 ", fmt::format("{:15%M:%S}", tp));
EXPECT_EQ("00:00.000000 ", fmt::format("{:{}%M:%S}", tp, 15));
EXPECT_EQ("00:00.000000 ", fmt::format("{:<15%M:%S}", tp));
EXPECT_EQ(" 00:00.000000 ", fmt::format("{:^15%M:%S}", tp));
EXPECT_EQ(" 00:00.000000", fmt::format("{:>15%M:%S}", tp));
EXPECT_EQ("00:00.000000***", fmt::format("{:*<15%M:%S}", tp));
EXPECT_EQ("*00:00.000000**", fmt::format("{:*^15%M:%S}", tp));
EXPECT_EQ("***00:00.000000", fmt::format("{:*>15%M:%S}", tp));
}
TEST(chrono_test, format_specs) {
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
@@ -418,6 +576,10 @@ TEST(chrono_test, invalid_specs) {
"invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
"invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:abc}"), sec), fmt::format_error,
"invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2f}"), sec), fmt::format_error,
"invalid format");
}
auto format_tm(const std::tm& time, fmt::string_view spec,
@@ -465,7 +627,7 @@ TEST(chrono_test, format_default_fp) {
TEST(chrono_test, format_precision) {
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
(void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
fmt::format_error, "precision not allowed for this argument type");
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@@ -610,12 +772,28 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
"-13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
// Check subsecond presision modifier.
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}),
"00.000001");
EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}),
"00.000001234000000000");
EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6),
"00.000001");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}),
"01.234000");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
"-01.234000");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400");
{
// Check that {:%H:%M:%S} is equivalent to {:%T}.
auto dur = std::chrono::milliseconds{3601234};
auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "01:00:01.234");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
}
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
@@ -629,15 +807,173 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
}
// Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
"07.000000");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 3>>(1)),
EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
"00.333333");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)),
EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 7>>(1)),
"00.142857");
EXPECT_EQ(
fmt::format("{:%S}",
std::chrono::duration<signed char, std::ratio<1, 100>>(0x80)),
"-01.28");
EXPECT_EQ(
fmt::format("{:%M:%S}",
std::chrono::duration<short, std::ratio<1, 100>>(0x8000)),
"-05:27.68");
// Check that floating point seconds with ratio<1,1> are printed.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
"01.500000");
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
"-01:01.250000");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
// (time zone database) is not shipped with many windows versions.
#if FMT_USE_UTC_TIME && !defined(_WIN32)
TEST(chrono_test, utc_clock) {
auto t1 = std::chrono::system_clock::now();
auto t1_utc = std::chrono::utc_clock::from_sys(t1);
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t1),
fmt::format("{:%Y-%m-%d %H:%M:%S}", t1_utc));
}
#endif
TEST(chrono_test, timestamps_sub_seconds) {
std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<long long, std::ratio<1, 3>>>
t1(std::chrono::duration<long long, std::ratio<1, 3>>(4));
EXPECT_EQ(fmt::format("{:%S}", t1), "01.333333");
std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<double, std::ratio<1, 3>>>
t2(std::chrono::duration<double, std::ratio<1, 3>>(4));
EXPECT_EQ(fmt::format("{:%S}", t2), "01.333333");
const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
t3(std::chrono::seconds(2));
EXPECT_EQ(fmt::format("{:%S}", t3), "02");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<double>>
t4(std::chrono::duration<double, std::ratio<1, 1>>(9.5));
EXPECT_EQ(fmt::format("{:%S}", t4), "09.500000");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<double>>
t5(std::chrono::duration<double, std::ratio<1, 1>>(9));
EXPECT_EQ(fmt::format("{:%S}", t5), "09");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>
t6(std::chrono::seconds(1) + std::chrono::milliseconds(120));
EXPECT_EQ(fmt::format("{:%S}", t6), "01.120");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::microseconds>
t7(std::chrono::microseconds(1234567));
EXPECT_EQ(fmt::format("{:%S}", t7), "01.234567");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::nanoseconds>
t8(std::chrono::nanoseconds(123456789));
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
const auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now());
const auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
fmt::format("{:%Y-%m-%d %T}", t9));
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>
t10(std::chrono::milliseconds(2000));
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
{
const auto epoch = std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>();
const auto d = std::chrono::milliseconds(250);
EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - d));
EXPECT_EQ("00.000", fmt::format("{:%S}", epoch));
EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d));
}
}
TEST(chrono_test, glibc_extensions) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
{
const auto d = std::chrono::hours(1) + std::chrono::minutes(2) +
std::chrono::seconds(3);
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3");
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3");
}
{
const auto tm = make_tm(1970, 1, 1, 1, 2, 3);
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3");
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3");
}
{
const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140);
EXPECT_EQ(fmt::format("{:%S}", d), "03.140");
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140");
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140");
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140");
}
{
const auto d = std::chrono::duration<double>(3.14);
EXPECT_EQ(fmt::format("{:%S}", d), "03.140000");
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000");
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000");
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
}
}

View File

@@ -1,6 +1,6 @@
# Test if compile errors are produced where necessary.
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.25)
project(compile-error-test CXX)
set(fmt_headers "
@@ -64,7 +64,7 @@ function (run_tests)
")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.25)
project(tests CXX)
add_subdirectory(${FMT_DIR} fmt)
${cmake_targets}

View File

@@ -304,8 +304,9 @@ TEST(compile_test, compile_format_string_literal) {
// (compiler file
// 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp',
// line 8635)
#if ((FMT_CPLUSPLUS >= 202002L) && \
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
#if ((FMT_CPLUSPLUS >= 202002L) && \
(!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
(FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {

View File

@@ -9,9 +9,7 @@
#include "test-assert.h"
// clang-format on
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
#include "fmt/core.h"
#undef I
#include <algorithm> // std::copy_n
#include <climits> // INT_MAX
@@ -72,6 +70,16 @@ TEST(string_view_test, compare) {
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
EXPECT_TRUE(string_view("foo").starts_with('f'));
EXPECT_FALSE(string_view("foo").starts_with('o'));
EXPECT_FALSE(string_view().starts_with('o'));
EXPECT_TRUE(string_view("foo").starts_with("fo"));
EXPECT_TRUE(string_view("foo").starts_with("foo"));
EXPECT_FALSE(string_view("foo").starts_with("fooo"));
EXPECT_FALSE(string_view().starts_with("fooo"));
check_op<std::equal_to>();
check_op<std::not_equal_to>();
check_op<std::less>();
@@ -80,24 +88,6 @@ TEST(string_view_test, compare) {
check_op<std::greater_equal>();
}
namespace test_ns {
template <typename Char> class test_string {
private:
std::basic_string<Char> s_;
public:
test_string(const Char* s) : s_(s) {}
const Char* data() const { return s_.data(); }
size_t length() const { return s_.size(); }
operator const Char*() const { return s_.c_str(); }
};
template <typename Char>
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
return {s.data(), s.length()};
}
} // namespace test_ns
TEST(core_test, is_output_iterator) {
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
@@ -151,7 +141,7 @@ TEST(buffer_test, indestructible) {
}
template <typename T> struct mock_buffer final : buffer<T> {
MOCK_METHOD1(do_grow, size_t(size_t capacity));
MOCK_METHOD(size_t, do_grow, (size_t));
void grow(size_t capacity) override {
this->set(this->data(), do_grow(capacity));
@@ -304,7 +294,7 @@ template <typename Char> struct formatter<test_struct, Char> {
return ctx.begin();
}
auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) {
auto format(test_struct, format_context& ctx) const -> decltype(ctx.out()) {
auto test = string_view("test");
return std::copy_n(test.data(), test.size(), ctx.out());
}
@@ -318,7 +308,7 @@ TEST(arg_test, format_args) {
TEST(arg_test, make_value_with_custom_context) {
auto t = test_struct();
fmt::detail::value<custom_context> arg(
auto arg = fmt::detail::value<custom_context>(
fmt::detail::arg_mapper<custom_context>().map(t));
auto ctx = custom_context();
auto parse_ctx = fmt::format_parse_context("");
@@ -337,12 +327,12 @@ template <typename T> struct mock_visitor {
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
}
MOCK_METHOD1_T(visit, test_result(T value));
MOCK_METHOD0_T(unexpected, void());
MOCK_METHOD(test_result, visit, (T));
MOCK_METHOD(void, unexpected, ());
test_result operator()(T value) { return visit(value); }
auto operator()(T value) -> test_result { return visit(value); }
template <typename U> test_result operator()(U) {
template <typename U> auto operator()(U) -> test_result {
unexpected();
return test_result();
}
@@ -394,13 +384,13 @@ using test_types =
TYPED_TEST_SUITE(numeric_arg_test, test_types);
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
T test_value() {
auto test_value() -> T {
return static_cast<T>(42);
}
template <typename T,
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
T test_value() {
auto test_value() -> T {
return static_cast<T>(4.2);
}
@@ -443,8 +433,8 @@ TEST(arg_test, pointer_arg) {
}
struct check_custom {
test_result operator()(
fmt::basic_format_arg<fmt::format_context>::handle h) const {
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
-> test_result {
struct test_buffer final : fmt::detail::buffer<char> {
char data[10];
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
@@ -463,13 +453,13 @@ TEST(arg_test, custom_arg) {
auto test = test_struct();
using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
testing::StrictMock<visitor> v;
auto&& v = testing::StrictMock<visitor>();
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
}
TEST(arg_test, visit_invalid_arg) {
testing::StrictMock<mock_visitor<fmt::monostate>> visitor;
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
EXPECT_CALL(visitor, visit(_));
auto arg = fmt::basic_format_arg<fmt::format_context>();
fmt::visit_format_arg(visitor, arg);
@@ -477,186 +467,78 @@ TEST(arg_test, visit_invalid_arg) {
#if FMT_USE_CONSTEXPR
enum class arg_id_result { none, empty, index, name, error };
enum class arg_id_result { none, empty, index, name };
struct test_arg_id_handler {
arg_id_result res = arg_id_result::none;
int index = 0;
string_view name;
constexpr void operator()() { res = arg_id_result::empty; }
constexpr void on_auto() { res = arg_id_result::empty; }
constexpr void operator()(int i) {
constexpr void on_index(int i) {
res = arg_id_result::index;
index = i;
}
constexpr void operator()(string_view n) {
constexpr void on_name(string_view n) {
res = arg_id_result::name;
name = n;
}
constexpr void on_error(const char*) { res = arg_id_result::error; }
};
template <size_t N>
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
test_arg_id_handler h;
auto h = test_arg_id_handler();
fmt::detail::parse_arg_id(s, s + N, h);
return h;
}
TEST(format_test, constexpr_parse_arg_id) {
TEST(core_test, constexpr_parse_arg_id) {
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
static_assert(parse_arg_id("42:").index == 42, "");
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
static_assert(parse_arg_id("foo:").name.size() == 3, "");
static_assert(parse_arg_id("!").res == arg_id_result::error, "");
}
struct test_format_specs_handler {
enum result { none, hash, zero, loc, error };
result res = none;
fmt::align_t alignment = fmt::align::none;
fmt::sign_t sign = fmt::sign::none;
char fill = 0;
int width = 0;
fmt::detail::arg_ref<char> width_ref;
int precision = 0;
fmt::detail::arg_ref<char> precision_ref;
fmt::presentation_type type = fmt::presentation_type::none;
// Workaround for MSVC2017 bug that results in "expression did not evaluate
// to a constant" with compiler-generated copy ctor.
constexpr test_format_specs_handler() {}
constexpr test_format_specs_handler(const test_format_specs_handler& other) =
default;
constexpr void on_align(fmt::align_t a) { alignment = a; }
constexpr void on_fill(fmt::string_view f) { fill = f[0]; }
constexpr void on_sign(fmt::sign_t s) { sign = s; }
constexpr void on_hash() { res = hash; }
constexpr void on_zero() { res = zero; }
constexpr void on_localized() { res = loc; }
constexpr void on_width(int w) { width = w; }
constexpr void on_dynamic_width(fmt::detail::auto_id) {}
constexpr void on_dynamic_width(int index) { width_ref = index; }
constexpr void on_dynamic_width(string_view) {}
constexpr void on_precision(int p) { precision = p; }
constexpr void on_dynamic_precision(fmt::detail::auto_id) {}
constexpr void on_dynamic_precision(int index) { precision_ref = index; }
constexpr void on_dynamic_precision(string_view) {}
constexpr void end_precision() {}
constexpr void on_type(fmt::presentation_type t) { type = t; }
constexpr void on_error(const char*) { res = error; }
};
template <size_t N>
constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) {
auto h = test_format_specs_handler();
fmt::detail::parse_format_specs(s, s + N - 1, h);
return h;
template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
auto ctx = fmt::detail::compile_parse_context<char>(fmt::string_view(s, N),
43, nullptr);
auto specs = fmt::detail::dynamic_format_specs<>();
fmt::detail::parse_format_specs(s, s + N - 1, specs, ctx,
fmt::detail::type::float_type);
return specs;
}
TEST(core_test, constexpr_parse_format_specs) {
using handler = test_format_specs_handler;
static_assert(parse_test_specs("<").alignment == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill == '*', "");
static_assert(parse_test_specs("<").align == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill[0] == '*', "");
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
static_assert(parse_test_specs("#").res == handler::hash, "");
static_assert(parse_test_specs("0").res == handler::zero, "");
static_assert(parse_test_specs("L").res == handler::loc, "");
static_assert(parse_test_specs("#").alt, "");
static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
static_assert(parse_test_specs("L").localized, "");
static_assert(parse_test_specs("42").width == 42, "");
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
static_assert(parse_test_specs(".42").precision == 42, "");
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, "");
static_assert(parse_test_specs("{<").res == handler::error, "");
}
struct test_parse_context {
using char_type = char;
constexpr int next_arg_id() { return 11; }
template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
FMT_CONSTEXPR void check_dynamic_spec(int) {}
constexpr const char* begin() { return nullptr; }
constexpr const char* end() { return nullptr; }
void on_error(const char*) {}
};
template <size_t N>
constexpr fmt::detail::dynamic_format_specs<char> parse_dynamic_specs(
const char (&s)[N]) {
auto specs = fmt::detail::dynamic_format_specs<char>();
auto ctx = test_parse_context();
auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx);
parse_format_specs(s, s + N - 1, h);
return specs;
}
TEST(format_test, constexpr_dynamic_specs_handler) {
static_assert(parse_dynamic_specs("<").align == fmt::align::left, "");
static_assert(parse_dynamic_specs("*^").fill[0] == '*', "");
static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, "");
static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, "");
static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, "");
static_assert(parse_dynamic_specs("#").alt, "");
static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, "");
static_assert(parse_dynamic_specs("42").width == 42, "");
static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, "");
static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, "");
static_assert(parse_dynamic_specs(".42").precision == 42, "");
static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, "");
static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec,
"");
}
template <size_t N>
constexpr test_format_specs_handler check_specs(const char (&s)[N]) {
fmt::detail::specs_checker<test_format_specs_handler> checker(
test_format_specs_handler(), fmt::detail::type::double_type);
parse_format_specs(s, s + N - 1, checker);
return checker;
}
TEST(format_test, constexpr_specs_checker) {
using handler = test_format_specs_handler;
static_assert(check_specs("<").alignment == fmt::align::left, "");
static_assert(check_specs("*^").fill == '*', "");
static_assert(check_specs("+").sign == fmt::sign::plus, "");
static_assert(check_specs("-").sign == fmt::sign::minus, "");
static_assert(check_specs(" ").sign == fmt::sign::space, "");
static_assert(check_specs("#").res == handler::hash, "");
static_assert(check_specs("0").res == handler::zero, "");
static_assert(check_specs("42").width == 42, "");
static_assert(check_specs("{42}").width_ref.val.index == 42, "");
static_assert(check_specs(".42").precision == 42, "");
static_assert(check_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(check_specs("d").type == fmt::presentation_type::dec, "");
static_assert(check_specs("{<").res == handler::error, "");
static_assert(
parse_test_specs("f").type == fmt::presentation_type::fixed_lower, "");
}
struct test_format_string_handler {
constexpr void on_text(const char*, const char*) {}
constexpr int on_arg_id() { return 0; }
constexpr auto on_arg_id() -> int { return 0; }
template <typename T> constexpr int on_arg_id(T) { return 0; }
template <typename T> constexpr auto on_arg_id(T) -> int { return 0; }
constexpr void on_replacement_field(int, const char*) {}
constexpr const char* on_format_specs(int, const char* begin, const char*) {
constexpr auto on_format_specs(int, const char* begin, const char*) -> const
char* {
return begin;
}
@@ -671,7 +553,7 @@ template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
return !h.error;
}
TEST(format_test, constexpr_parse_format_string) {
TEST(core_test, constexpr_parse_format_string) {
static_assert(parse_string("foo"), "");
static_assert(!parse_string("}"), "");
static_assert(parse_string("{}"), "");
@@ -693,7 +575,8 @@ template <> struct formatter<enabled_formatter> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) {
auto format(enabled_formatter, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
};
@@ -702,7 +585,7 @@ template <> struct formatter<enabled_ptr_formatter*> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(enabled_ptr_formatter*, format_context& ctx)
auto format(enabled_ptr_formatter*, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
@@ -771,29 +654,17 @@ FMT_END_NAMESPACE
enum class unformattable_scoped_enum {};
namespace test {
enum class formattable_scoped_enum {};
auto format_as(formattable_scoped_enum) -> int { return 42; }
struct convertible_to_enum {
operator formattable_scoped_enum() const { return {}; }
};
} // namespace test
TEST(core_test, is_formattable) {
#if 0
// This should be enabled once corresponding map overloads are gone.
static_assert(fmt::is_formattable<signed char*>::value, "");
static_assert(fmt::is_formattable<unsigned char*>::value, "");
static_assert(fmt::is_formattable<const signed char*>::value, "");
static_assert(fmt::is_formattable<const unsigned char*>::value, "");
#endif
static_assert(!fmt::is_formattable<wchar_t>::value, "");
#ifdef __cpp_char8_t
static_assert(!fmt::is_formattable<char8_t>::value, "");
#endif
static_assert(!fmt::is_formattable<char16_t>::value, "");
static_assert(!fmt::is_formattable<char32_t>::value, "");
static_assert(!fmt::is_formattable<signed char*>::value, "");
static_assert(!fmt::is_formattable<unsigned char*>::value, "");
static_assert(!fmt::is_formattable<const signed char*>::value, "");
static_assert(!fmt::is_formattable<const unsigned char*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
@@ -801,7 +672,8 @@ TEST(core_test, is_formattable) {
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
"");
static_assert(fmt::is_formattable<const_formattable&>::value, "");
static_assert(fmt::is_formattable<const const_formattable&>::value, "");
@@ -821,27 +693,28 @@ TEST(core_test, is_formattable) {
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
static_assert(fmt::is_formattable<test::formattable_scoped_enum>::value, "");
static_assert(!fmt::is_formattable<test::convertible_to_enum>::value, "");
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
}
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
TEST(core_test, format_to) {
std::string s;
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", 42);
EXPECT_EQ(s, "42");
}
TEST(core_test, format_as) {
EXPECT_EQ(fmt::format("{}", test::formattable_scoped_enum()), "42");
#ifdef __cpp_lib_byte
TEST(core_test, format_byte) {
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
}
#endif
struct convertible_to_int {
operator int() const { return 42; }
};
struct convertible_to_c_string {
struct convertible_to_cstring {
operator const char*() const { return "foo"; }
};
@@ -850,16 +723,17 @@ template <> struct formatter<convertible_to_int> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) {
auto format(convertible_to_int, format_context& ctx) const
-> decltype(ctx.out()) {
return std::copy_n("foo", 3, ctx.out());
}
};
template <> struct formatter<convertible_to_c_string> {
template <> struct formatter<convertible_to_cstring> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(convertible_to_c_string, format_context& ctx)
auto format(convertible_to_cstring, format_context& ctx) const
-> decltype(ctx.out()) {
return std::copy_n("bar", 3, ctx.out());
}
@@ -868,7 +742,7 @@ FMT_END_NAMESPACE
TEST(core_test, formatter_overrides_implicit_conversion) {
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar");
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "bar");
}
// Test that check is not found by ADL.
@@ -877,27 +751,26 @@ TEST(core_test, adl_check) {
EXPECT_EQ(fmt::format("{}", test_struct()), "test");
}
TEST(core_test, to_string_view_foreign_strings) {
using namespace test_ns;
EXPECT_EQ(to_string_view(test_string<char>("42")), "42");
fmt::detail::type type =
fmt::detail::mapped_type_constant<test_string<char>,
fmt::format_context>::value;
EXPECT_EQ(type, fmt::detail::type::string_type);
}
struct implicitly_convertible_to_string {
operator std::string() const { return "foo"; }
};
struct implicitly_convertible_to_string_view {
operator fmt::string_view() const { return "foo"; }
};
TEST(core_test, format_implicitly_convertible_to_string_view) {
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
TEST(core_test, no_implicit_conversion_to_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_string_view>::value);
}
#ifdef FMT_USE_STRING_VIEW
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(core_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
#endif
// std::is_constructible is broken in MSVC until version 2015.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
struct explicitly_convertible_to_string_view {
@@ -926,25 +799,6 @@ TEST(core_test, format_explicitly_convertible_to_std_string_view) {
# endif
#endif
struct convertible_to_long_long {
operator long long() const { return 1LL << 32; }
};
TEST(format_test, format_convertible_to_long_long) {
EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long()));
}
struct disabled_rvalue_conversion {
operator const char*() const& { return "foo"; }
operator const char*() & { return "foo"; }
operator const char*() const&& = delete;
operator const char*() && = delete;
};
TEST(core_test, disabled_rvalue_conversion) {
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
}
namespace adl_test {
template <typename... T> void make_format_args(const T&...) = delete;

View File

@@ -8,12 +8,14 @@
#include <iterator>
#include <vector>
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include "fmt/xchar.h"
#undef I
// Exercise the API to verify that everything we expect to can compile.
void test_format_api() {

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.25)
project(fmt-test)

View File

@@ -242,11 +242,11 @@ TEST(fp_test, dragonbox_max_k) {
floor_log10_pow2(std::numeric_limits<float>::min_exponent -
fmt::detail::num_significand_bits<float>() - 1));
using double_info = fmt::detail::dragonbox::float_info<double>;
EXPECT_EQ(
fmt::detail::const_check(double_info::max_k),
double_info::kappa -
floor_log10_pow2(std::numeric_limits<double>::min_exponent -
fmt::detail::num_significand_bits<double>() - 1));
EXPECT_EQ(fmt::detail::const_check(double_info::max_k),
double_info::kappa -
floor_log10_pow2(
std::numeric_limits<double>::min_exponent -
2 * fmt::detail::num_significand_bits<double>() - 1));
}
TEST(fp_test, get_round_direction) {
@@ -301,7 +301,7 @@ TEST(format_impl_test, format_error_code) {
std::string msg = "error 42", sep = ": ";
{
auto buffer = fmt::memory_buffer();
format_to(fmt::appender(buffer), "garbage");
fmt::format_to(fmt::appender(buffer), "garbage");
fmt::detail::format_error_code(buffer, 42, "test");
EXPECT_EQ(to_string(buffer), "test: " + msg);
}
@@ -353,6 +353,16 @@ TEST(format_impl_test, count_digits) {
test_count_digits<uint64_t>();
}
TEST(format_impl_test, countl_zero) {
constexpr auto num_bits = fmt::detail::num_bits<uint32_t>();
uint32_t n = 1u;
for (int i = 1; i < num_bits - 1; i++) {
n <<= 1;
EXPECT_EQ(fmt::detail::countl_zero(n - 1), num_bits - i);
EXPECT_EQ(fmt::detail::countl_zero(n), num_bits - i - 1);
}
}
#if FMT_USE_FLOAT128
TEST(format_impl_test, write_float128) {
auto s = std::string();
@@ -372,6 +382,8 @@ struct double_double {
auto operator-() const -> double_double { return double_double(-a, -b); }
};
auto format_as(double_double d) -> double { return d; }
bool operator>=(const double_double& lhs, const double_double& rhs) {
return lhs.a + lhs.b >= rhs.a + rhs.b;
}
@@ -384,6 +396,8 @@ struct slow_float {
auto operator-() const -> slow_float { return slow_float(-value); }
};
auto format_as(slow_float f) -> float { return f; }
namespace std {
template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct numeric_limits<double_double> {
@@ -545,3 +559,10 @@ TEST(format_impl_test, utf8_decode_bogus_byte_sequences) {
EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c
EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len);
}
TEST(format_impl_test, unicode_to_utf8) {
auto s = std::string("ёжик");
fmt::detail::unicode_to_utf8<wchar_t> u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}

View File

@@ -646,7 +646,7 @@ TEST(format_test, fill) {
fmt::format(string_view("{:\0>4}", 6), '*'));
EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42));
EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0),
format_error, "invalid type specifier");
format_error, "invalid format specifier");
}
TEST(format_test, plus_sign) {
@@ -654,27 +654,25 @@ TEST(format_test, plus_sign) {
EXPECT_EQ("-42", fmt::format("{0:+}", -42));
EXPECT_EQ("+42", fmt::format("{0:+}", 42));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ("+42", fmt::format("{0:+}", 42l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ("+42", fmt::format("{0:+}", 42ll));
#if FMT_USE_INT128
EXPECT_EQ("+42", fmt::format("{0:+}", __int128_t(42)));
#endif
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0));
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+"), 'c'), format_error,
"missing '}' in format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 'c'), format_error,
"invalid format specifier for char");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error,
"format specifier requires numeric argument");
"invalid format specifier");
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:+}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
}
TEST(format_test, minus_sign) {
@@ -682,24 +680,22 @@ TEST(format_test, minus_sign) {
EXPECT_EQ("-42", fmt::format("{0:-}", -42));
EXPECT_EQ("42", fmt::format("{0:-}", 42));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ("42", fmt::format("{0:-}", 42l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ("42", fmt::format("{0:-}", 42ll));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ("42", fmt::format("{0:-}", 42.0));
EXPECT_EQ("42", fmt::format("{0:-}", 42.0l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-"), 'c'), format_error,
"missing '}' in format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 'c'), format_error,
"invalid format specifier for char");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error,
"format specifier requires numeric argument");
"invalid format specifier");
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:-}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
}
TEST(format_test, space_sign) {
@@ -707,24 +703,22 @@ TEST(format_test, space_sign) {
EXPECT_EQ("-42", fmt::format("{0: }", -42));
EXPECT_EQ(" 42", fmt::format("{0: }", 42));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ(" 42", fmt::format("{0: }", 42l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ(" 42", fmt::format("{0: }", 42ll));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error,
"format specifier requires signed argument");
"invalid format specifier");
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0));
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: "), 'c'), format_error,
"missing '}' in format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 'c'), format_error,
"invalid format specifier for char");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error,
"format specifier requires numeric argument");
"invalid format specifier");
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0: }"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
}
TEST(format_test, hash_flag) {
@@ -761,8 +755,8 @@ TEST(format_test, hash_flag) {
EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull));
EXPECT_EQ("042", fmt::format("{0:#o}", 042ull));
EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0));
EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0l));
EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0));
EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0l));
EXPECT_EQ("4.e+01", fmt::format("{:#.0e}", 42.0));
EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01));
EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5));
@@ -772,10 +766,10 @@ TEST(format_test, hash_flag) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
"invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
"format specifier requires numeric argument");
"invalid format specifier");
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:#}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
}
TEST(format_test, zero_flag) {
@@ -799,6 +793,17 @@ TEST(format_test, zero_flag) {
format_error, "format specifier requires numeric argument");
}
TEST(format_test, zero_flag_and_align) {
// If the 0 character and an align option both appear, the 0 character is
// ignored.
EXPECT_EQ("42 ", fmt::format("{0:<05}", 42));
EXPECT_EQ("-42 ", fmt::format("{0:<05}", -42));
EXPECT_EQ(" 42 ", fmt::format("{0:^05}", 42));
EXPECT_EQ(" -42 ", fmt::format("{0:^05}", -42));
EXPECT_EQ(" 42", fmt::format("{0:>05}", 42));
EXPECT_EQ(" -42", fmt::format("{0:>05}", -42));
}
TEST(format_test, width) {
char format_str[buffer_size];
safe_sprintf(format_str, "{0:%u", UINT_MAX);
@@ -831,9 +836,9 @@ TEST(format_test, width) {
EXPECT_EQ("str ", fmt::format("{0:12}", "str"));
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.0");
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
EXPECT_EQ(fmt::format("{:6c}", static_cast<int>('x')), "x ");
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), "000000");
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
}
TEST(format_test, runtime_width) {
@@ -902,54 +907,54 @@ TEST(format_test, precision) {
char format_str[buffer_size];
safe_sprintf(format_str, "{0:.%u", UINT_MAX);
increment(format_str + 4);
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"number is too big");
size_t size = std::strlen(format_str);
format_str[size] = '}';
format_str[size + 1] = 0;
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"number is too big");
safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u);
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"number is too big");
safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u);
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0), format_error,
"missing precision specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0), format_error,
"missing precision specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error,
"invalid precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error,
"invalid precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42u), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42u), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42l), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42l), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ul), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ul), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ll), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ll), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ull), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ull), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345));
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l));
EXPECT_EQ("1.2e+56", fmt::format("{:.2}", 1.234e56));
@@ -1028,10 +1033,10 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)),
format_error, "precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
format_error, "precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
@@ -1047,97 +1052,86 @@ TEST(format_test, runtime_precision) {
char format_str[buffer_size];
safe_sprintf(format_str, "{0:.{%u", UINT_MAX);
increment(format_str + 5);
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"invalid format string");
size_t size = std::strlen(format_str);
format_str[size] = '}';
format_str[size + 1] = 0;
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"argument not found");
format_str[size + 1] = '}';
format_str[size + 2] = 0;
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
"argument not found");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0.0), format_error,
"invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0.0), format_error,
"cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0.0), format_error,
"invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}"), 0, 0), format_error,
"precision not allowed for this argument type");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0), format_error,
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0), format_error,
"argument not found");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0), format_error,
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0.0), format_error,
"invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1), format_error,
"negative precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)),
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1),
format_error, "negative precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)),
format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error,
"negative precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l),
format_error, "negative precision");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX;
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (value + 1)),
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)),
format_error, "number is too big");
}
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)),
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)),
format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error,
"precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error,
"precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'),
format_error, "precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0),
format_error, "precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42u, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error,
"precision not allowed for this argument type");
"invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42l, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ul, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ul, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ll, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ll, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ull, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ull, 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2));
EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"),
reinterpret_cast<void*>(0xcafe), 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"),
reinterpret_cast<void*>(0xcafe), 2),
format_error,
"precision not allowed for this argument type");
format_error, "invalid format specifier");
EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2));
}
@@ -1167,7 +1161,7 @@ void check_unknown_types(const T& value, const char* types, const char*) {
char c = static_cast<char>(i);
if (std::strchr(types, c) || std::strchr(special, c) || !c) continue;
safe_sprintf(format_str, "{0:10%c}", c);
const char* message = "invalid type specifier";
const char* message = "invalid format specifier";
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), value),
format_error, message)
<< format_str << " " << message;
@@ -1176,7 +1170,7 @@ void check_unknown_types(const T& value, const char* types, const char*) {
TEST(format_test, format_int) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error,
"invalid type specifier");
"invalid format specifier");
check_unknown_types(42, "bBdoxXnLc", "integer");
EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x')));
}
@@ -1205,6 +1199,7 @@ constexpr auto uint128_max = ~static_cast<__uint128_t>(0);
TEST(format_test, format_dec) {
EXPECT_EQ("0", fmt::format("{0}", 0));
EXPECT_EQ("42", fmt::format("{0}", 42));
EXPECT_EQ("42>", fmt::format("{:}>", 42));
EXPECT_EQ("42", fmt::format("{0:d}", 42));
EXPECT_EQ("42", fmt::format("{0}", 42u));
EXPECT_EQ("-42", fmt::format("{0}", -42));
@@ -1343,16 +1338,64 @@ TEST(format_test, format_double) {
EXPECT_EQ(fmt::format("{:f}", 392.65), "392.650000");
EXPECT_EQ(fmt::format("{:F}", 392.65), "392.650000");
EXPECT_EQ(fmt::format("{:L}", 42.0), "42");
EXPECT_EQ(fmt::format("{:24a}", 4.2f), " 0x1.0cccccp+2");
EXPECT_EQ(fmt::format("{:24a}", 4.2), " 0x1.0cccccccccccdp+2");
EXPECT_EQ(fmt::format("{:<24a}", 4.2), "0x1.0cccccccccccdp+2 ");
EXPECT_EQ(fmt::format("{0:e}", 392.65), "3.926500e+02");
EXPECT_EQ(fmt::format("{0:E}", 392.65), "3.926500E+02");
EXPECT_EQ(fmt::format("{0:+010.4g}", 392.65), "+0000392.6");
char buffer[buffer_size];
safe_sprintf(buffer, "%a", -42.0);
EXPECT_EQ(fmt::format("{:a}", -42.0), buffer);
safe_sprintf(buffer, "%A", -42.0);
EXPECT_EQ(fmt::format("{:A}", -42.0), buffer);
#if FMT_CPLUSPLUS >= 201703L
double xd = 0x1.ffffffffffp+2;
safe_sprintf(buffer, "%.*a", 10, xd);
EXPECT_EQ(fmt::format("{:.10a}", xd), buffer);
safe_sprintf(buffer, "%.*a", 9, xd);
EXPECT_EQ(fmt::format("{:.9a}", xd), buffer);
if (std::numeric_limits<long double>::digits == 64) {
auto ld = 0xf.ffffffffffp-3l;
safe_sprintf(buffer, "%La", ld);
EXPECT_EQ(fmt::format("{:a}", ld), buffer);
safe_sprintf(buffer, "%.*La", 10, ld);
EXPECT_EQ(fmt::format("{:.10a}", ld), buffer);
safe_sprintf(buffer, "%.*La", 9, ld);
EXPECT_EQ(fmt::format("{:.9a}", ld), buffer);
}
#endif
if (fmt::detail::const_check(std::numeric_limits<double>::is_iec559)) {
double d = (std::numeric_limits<double>::min)();
EXPECT_EQ(fmt::format("{:a}", d), "0x1p-1022");
EXPECT_EQ(fmt::format("{:#a}", d), "0x1.p-1022");
d = (std::numeric_limits<double>::max)();
safe_sprintf(buffer, "%a", d);
EXPECT_EQ(fmt::format("{:a}", d), buffer);
d = std::numeric_limits<double>::denorm_min();
EXPECT_EQ(fmt::format("{:a}", d), "0x0.0000000000001p-1022");
}
if (std::numeric_limits<long double>::digits == 64) {
auto ld = (std::numeric_limits<long double>::min)();
safe_sprintf(buffer, "%La", ld);
EXPECT_EQ(fmt::format("{:a}", ld), buffer);
ld = (std::numeric_limits<long double>::max)();
safe_sprintf(buffer, "%La", ld);
EXPECT_EQ(fmt::format("{:a}", ld), buffer);
ld = std::numeric_limits<long double>::denorm_min();
EXPECT_EQ(fmt::format("{:a}", ld), "0x0.000000000000001p-16382");
}
safe_sprintf(buffer, "%.*a", 10, 4.2);
EXPECT_EQ(fmt::format("{:.10a}", 4.2), buffer);
EXPECT_EQ(fmt::format("{:a}", -42.0), "-0x1.5p+5");
EXPECT_EQ(fmt::format("{:A}", -42.0), "-0X1.5P+5");
EXPECT_EQ(fmt::format("{:f}", 9223372036854775807.0),
"9223372036854775808.000000");
}
@@ -1446,8 +1489,15 @@ TEST(format_test, format_long_double) {
safe_sprintf(buffer, "%Le", 392.65l);
EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65l));
EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.64l));
safe_sprintf(buffer, "%La", 3.31l);
EXPECT_EQ(buffer, fmt::format("{:a}", 3.31l));
auto ld = 3.31l;
if (fmt::detail::is_double_double<decltype(ld)>::value) {
safe_sprintf(buffer, "%a", static_cast<double>(ld));
EXPECT_EQ(buffer, fmt::format("{:a}", ld));
} else {
safe_sprintf(buffer, "%La", ld);
EXPECT_EQ(buffer, fmt::format("{:a}", ld));
}
}
TEST(format_test, format_char) {
@@ -1466,6 +1516,7 @@ TEST(format_test, format_char) {
EXPECT_EQ("\n", fmt::format("{}", '\n'));
EXPECT_EQ("'\\n'", fmt::format("{:?}", '\n'));
EXPECT_EQ("ff", fmt::format("{:x}", '\xff'));
}
TEST(format_test, format_volatile_char) {
@@ -1507,6 +1558,12 @@ TEST(format_test, format_pointer) {
std::unique_ptr<int> up(new int(1));
EXPECT_EQ(fmt::format("{}", fmt::ptr(up.get())),
fmt::format("{}", fmt::ptr(up)));
struct custom_deleter {
void operator()(int* p) const { delete p; }
};
std::unique_ptr<int, custom_deleter> upcd(new int(1));
EXPECT_EQ(fmt::format("{}", fmt::ptr(upcd.get())),
fmt::format("{}", fmt::ptr(upcd)));
std::shared_ptr<int> sp(new int(1));
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
fmt::format("{}", fmt::ptr(sp)));
@@ -1581,7 +1638,7 @@ struct fmt::formatter<explicitly_convertible_to_std_string_view>
: formatter<std::string_view> {
auto format(explicitly_convertible_to_std_string_view v, format_context& ctx)
-> decltype(ctx.out()) {
return format_to(ctx.out(), "'{}'", std::string_view(v));
return fmt::format_to(ctx.out(), "'{}'", std::string_view(v));
}
};
@@ -1591,27 +1648,6 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
}
#endif
struct converible_to_anything {
template <typename T> operator T() const { return T(); }
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<converible_to_anything> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(converible_to_anything, format_context& ctx)
-> decltype(ctx.out()) {
return format_to(ctx.out(), "foo");
}
};
FMT_END_NAMESPACE
TEST(format_test, format_convertible_to_anything) {
EXPECT_EQ("foo", fmt::format("{}", converible_to_anything()));
}
class Answer {};
FMT_BEGIN_NAMESPACE
@@ -1624,7 +1660,8 @@ template <> struct formatter<date> {
}
auto format(const date& d, format_context& ctx) -> decltype(ctx.out()) {
format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day());
// Namespace-qualify to avoid ambiguity with std::format_to.
fmt::format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day());
return ctx.out();
}
};
@@ -1665,7 +1702,7 @@ TEST(format_test, format_examples) {
EXPECT_EQ("42", fmt::format("{}", 42));
memory_buffer out;
format_to(std::back_inserter(out), "The answer is {}.", 42);
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
EXPECT_EQ("The answer is 42.", to_string(out));
const char* filename = "nonexistent";
@@ -1717,7 +1754,7 @@ TEST(format_test, format_examples) {
EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42));
EXPECT_THROW_MSG(
(void)fmt::format(runtime("The answer is {:d}"), "forty-two"),
format_error, "invalid type specifier");
format_error, "invalid format specifier");
EXPECT_WRITE(
stdout, fmt::print("{}", std::numeric_limits<double>::infinity()), "inf");
@@ -1727,6 +1764,9 @@ TEST(format_test, print) {
EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!");
EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
"Don't panic!");
EXPECT_WRITE(stdout, fmt::println("Don't {}!", "panic"), "Don't panic!\n");
EXPECT_WRITE(stderr, fmt::println(stderr, "Don't {}!", "panic"),
"Don't panic!\n");
}
TEST(format_test, variadic) {
@@ -1789,12 +1829,6 @@ TEST(format_test, join) {
}
#ifdef __cpp_lib_byte
TEST(format_test, format_byte) {
using arg_mapper = fmt::detail::arg_mapper<fmt::format_context>;
EXPECT_EQ(arg_mapper().map(std::byte(42)), 42);
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
}
TEST(format_test, join_bytes) {
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
EXPECT_EQ(fmt::format("{}", fmt::join(v, ", ")), "1, 2, 3");
@@ -1803,7 +1837,7 @@ TEST(format_test, join_bytes) {
std::string vformat_message(int id, const char* format, fmt::format_args args) {
auto buffer = fmt::memory_buffer();
format_to(fmt::appender(buffer), "[{}] ", id);
fmt::format_to(fmt::appender(buffer), "[{}] ", id);
vformat_to(fmt::appender(buffer), format, args);
return to_string(buffer);
}
@@ -1832,9 +1866,6 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
}
struct string_like {};
fmt::string_view to_string_view(string_like) { return "foo"; }
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
@@ -1843,7 +1874,6 @@ static constexpr const char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) {
EXPECT_EQ("foo", fmt::format(FMT_STRING("foo")));
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
using namespace fmt::literals;
@@ -1852,6 +1882,7 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ("", fmt::format(FMT_STRING("")));
EXPECT_EQ("", fmt::format(FMT_STRING(""), "arg"_a = 42));
EXPECT_EQ("42", fmt::format(FMT_STRING("{answer}"), "answer"_a = Answer()));
EXPECT_EQ("1 2", fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2));
#endif
(void)static_with_null;
@@ -1917,46 +1948,6 @@ TEST(format_test, non_null_terminated_format_string) {
EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42));
}
struct variant {
enum { int_type, string_type } type;
explicit variant(int) : type(int_type) {}
explicit variant(const char*) : type(string_type) {}
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<variant> : dynamic_formatter<> {
auto format(variant value, format_context& ctx) -> decltype(ctx.out()) {
if (value.type == variant::int_type)
return dynamic_formatter<>::format(42, ctx);
return dynamic_formatter<>::format("foo", ctx);
}
};
FMT_END_NAMESPACE
TEST(format_test, dynamic_formatter) {
auto num = variant(42);
auto str = variant("foo");
EXPECT_EQ("42", fmt::format("{:d}", num));
EXPECT_EQ("foo", fmt::format("{:s}", str));
EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}}"), num), format_error,
"cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:{0}}"), num), format_error,
"cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:+}"), str), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:-}"), str), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG((void)fmt::format(runtime("{: }"), str), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:#}"), str), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:0}"), str), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2}"), num), format_error,
"precision not allowed for this argument type");
}
namespace adl_test {
namespace fmt {
namespace detail {
@@ -1969,30 +1960,26 @@ template <typename, typename OutputIt> void write(OutputIt, foo) = delete;
FMT_BEGIN_NAMESPACE
template <>
struct formatter<adl_test::fmt::detail::foo> : formatter<std::string> {
template <typename FormatContext>
auto format(adl_test::fmt::detail::foo, FormatContext& ctx)
auto format(adl_test::fmt::detail::foo, format_context& ctx)
-> decltype(ctx.out()) {
return formatter<std::string>::format("foo", ctx);
}
};
FMT_END_NAMESPACE
struct convertible_to_int {
operator int() const { return value; }
int value = 42;
};
TEST(format_test, to_string) {
EXPECT_EQ(fmt::to_string(42), "42");
EXPECT_EQ(fmt::to_string(reinterpret_cast<void*>(0x1234)), "0x1234");
EXPECT_EQ(fmt::to_string(adl_test::fmt::detail::foo()), "foo");
EXPECT_EQ(fmt::to_string(convertible_to_int()), "42");
EXPECT_EQ(fmt::to_string(foo), "0");
#if FMT_USE_FLOAT128
EXPECT_EQ(fmt::to_string(__float128(0.5)), "0.5");
#endif
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::to_string(std::string_view()), "");
#endif
}
TEST(format_test, output_iterators) {
@@ -2006,6 +1993,7 @@ TEST(format_test, output_iterators) {
TEST(format_test, formatted_size) {
EXPECT_EQ(2u, fmt::formatted_size("{}", 42));
EXPECT_EQ(2u, fmt::formatted_size(std::locale(), "{}", 42));
}
TEST(format_test, format_to_no_args) {
@@ -2108,116 +2096,6 @@ TEST(format_test, format_to_n_output_iterator) {
EXPECT_STREQ(buf, "42");
}
#if FMT_USE_CONSTEXPR
struct test_error_handler {
const char*& error;
FMT_CONSTEXPR test_error_handler(const char*& err) : error(err) {}
FMT_CONSTEXPR test_error_handler(const test_error_handler& other)
: error(other.error) {}
FMT_CONSTEXPR void on_error(const char* message) {
if (!error) error = message;
}
};
FMT_CONSTEXPR size_t len(const char* s) {
size_t len = 0;
while (*s++) ++len;
return len;
}
FMT_CONSTEXPR bool equal(const char* s1, const char* s2) {
if (!s1 || !s2) return s1 == s2;
while (*s1 && *s1 == *s2) {
++s1;
++s2;
}
return *s1 == *s2;
}
template <typename... Args>
FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
const char* actual_error = nullptr;
auto s = string_view(fmt, len(fmt));
auto checker =
fmt::detail::format_string_checker<char, test_error_handler, Args...>(
s, test_error_handler(actual_error));
fmt::detail::parse_format_string<true>(s, checker);
return equal(actual_error, expected_error);
}
# define EXPECT_ERROR_NOARGS(fmt, error) \
static_assert(test_error(fmt, error), "")
# define EXPECT_ERROR(fmt, error, ...) \
static_assert(test_error<__VA_ARGS__>(fmt, error), "")
TEST(format_test, format_string_errors) {
EXPECT_ERROR_NOARGS("foo", nullptr);
EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string");
EXPECT_ERROR("{0:s", "unknown format specifier", date);
# if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1916
// This causes an detail compiler error in MSVC2017.
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
EXPECT_ERROR("{:10000000000}", "number is too big", int);
EXPECT_ERROR("{:.10000000000}", "number is too big", int);
EXPECT_ERROR_NOARGS("{:x}", "argument not found");
EXPECT_ERROR("{:+}", "format specifier requires numeric argument",
const char*);
EXPECT_ERROR("{:-}", "format specifier requires numeric argument",
const char*);
EXPECT_ERROR("{:#}", "format specifier requires numeric argument",
const char*);
EXPECT_ERROR("{: }", "format specifier requires numeric argument",
const char*);
EXPECT_ERROR("{:0}", "format specifier requires numeric argument",
const char*);
EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned);
EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned);
EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned);
EXPECT_ERROR("{:{}}", "argument not found", int);
EXPECT_ERROR("{:.{}}", "argument not found", double);
EXPECT_ERROR("{:{}}", "width/precision is not integer", int, double);
EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int);
EXPECT_ERROR("{:s}", "invalid type specifier", int);
EXPECT_ERROR("{:s}", "invalid type specifier", char);
EXPECT_ERROR("{:+}", "invalid format specifier for char", char);
EXPECT_ERROR("{:s}", "invalid type specifier", double);
EXPECT_ERROR("{:d}", "invalid type specifier", const char*);
EXPECT_ERROR("{:d}", "invalid type specifier", std::string);
EXPECT_ERROR("{:s}", "invalid type specifier", void*);
# else
fmt::print("warning: constexpr is broken in this version of MSVC\n");
# endif
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
using namespace fmt::literals;
EXPECT_ERROR("{foo}", "named argument is not found", decltype("bar"_a = 42));
EXPECT_ERROR("{foo}", "named argument is not found",
decltype(fmt::arg("foo", 42)));
# else
EXPECT_ERROR("{foo}",
"compile-time checks for named arguments require C++20 support",
int);
# endif
EXPECT_ERROR_NOARGS("{10000000000}", "argument not found");
EXPECT_ERROR_NOARGS("{0x}", "invalid format string");
EXPECT_ERROR_NOARGS("{-}", "invalid format string");
EXPECT_ERROR("{:{0x}}", "invalid format string", int);
EXPECT_ERROR("{:{-}}", "invalid format string", int);
EXPECT_ERROR("{:.{0x}}", "invalid format string", int);
EXPECT_ERROR("{:.{-}}", "invalid format string", int);
EXPECT_ERROR("{:.x}", "missing precision specifier", int);
EXPECT_ERROR_NOARGS("{}", "argument not found");
EXPECT_ERROR("{1}", "argument not found", int);
EXPECT_ERROR("{1}{}",
"cannot switch from manual to automatic argument indexing", int,
int);
EXPECT_ERROR("{}{1}",
"cannot switch from automatic to manual argument indexing", int,
int);
}
TEST(format_test, vformat_to) {
using context = fmt::format_context;
fmt::basic_format_arg<context> arg = fmt::detail::make_arg<context>(42);
@@ -2230,8 +2108,6 @@ TEST(format_test, vformat_to) {
EXPECT_EQ("42", s);
}
#endif // FMT_USE_CONSTEXPR
TEST(format_test, char_traits_is_not_ambiguous) {
// Test that we don't inject detail names into the std namespace.
using namespace std;
@@ -2267,6 +2143,27 @@ TEST(format_test, back_insert_slicing) {
EXPECT_EQ(fmt::format("{}", check_back_appender{}), "y");
}
namespace test {
enum class scoped_enum_as_int {};
auto format_as(scoped_enum_as_int) -> int { return 42; }
enum class scoped_enum_as_string_view {};
auto format_as(scoped_enum_as_string_view) -> fmt::string_view { return "foo"; }
enum class scoped_enum_as_string {};
auto format_as(scoped_enum_as_string) -> std::string { return "foo"; }
struct struct_as_int {};
auto format_as(struct_as_int) -> int { return 42; }
} // namespace test
TEST(format_test, format_as) {
EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_int()), "42");
EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string_view()), "foo");
EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string()), "foo");
EXPECT_EQ(fmt::format("{}", test::struct_as_int()), "42");
}
template <typename Char, typename T> bool check_enabled_formatter() {
static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value,
"");
@@ -2309,3 +2206,62 @@ TEST(format_int_test, format_int) {
os << max_value<int64_t>();
EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
}
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
class format_facet : public fmt::format_facet<std::locale> {
protected:
struct int_formatter {
fmt::appender out;
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integer<T>::value)>
auto operator()(T value) -> bool {
fmt::format_to(out, "[{}]", value);
return true;
}
template <typename T, FMT_ENABLE_IF(!fmt::detail::is_integer<T>::value)>
auto operator()(T) -> bool {
return false;
}
};
auto do_put(fmt::appender out, fmt::loc_value val,
const fmt::format_specs<>&) const -> bool override;
};
auto format_facet::do_put(fmt::appender out, fmt::loc_value val,
const fmt::format_specs<>&) const -> bool {
return val.visit(int_formatter{out});
}
TEST(format_test, format_facet) {
auto loc = std::locale(std::locale(), new format_facet());
EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]");
EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]");
}
TEST(format_test, format_facet_separator) {
// U+2019 RIGHT SINGLE QUOTATION MARK is a digit separator in the de_CH
// locale.
auto loc =
std::locale({}, new fmt::format_facet<std::locale>("\xe2\x80\x99"));
EXPECT_EQ(fmt::format(loc, "{:L}", 1000),
"1\xe2\x80\x99"
"000");
}
TEST(format_test, format_facet_grouping) {
auto loc =
std::locale({}, new fmt::format_facet<std::locale>(",", {1, 2, 3}));
EXPECT_EQ(fmt::format(loc, "{:L}", 1234567890), "1,234,567,89,0");
}
TEST(format_test, format_named_arg_with_locale) {
EXPECT_EQ(fmt::format(std::locale(), "{answer}", fmt::arg("answer", 42)),
"42");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR

View File

@@ -22,7 +22,7 @@ function(add_fuzzer source)
if (FMT_FUZZ_LDFLAGS)
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
endif ()
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
target_compile_features(${name} PRIVATE cxx_std_14)
endfunction()
foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)

View File

@@ -201,6 +201,7 @@ TEST(gtest_extra_test, expect_write_streaming) {
// EXPECT_THROW_MSG macro.
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
int n = 0;
(void)n;
using std::runtime_error;
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
@@ -213,6 +214,7 @@ TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
// EXPECT_SYSTEM_ERROR macro.
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
int n = 0;
(void)n;
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, ""), "");

View File

@@ -12,12 +12,7 @@
#include <string>
#ifdef FMT_MODULE_TEST
import fmt;
#else
# include "fmt/os.h"
#endif // FMG_MODULE_TEST
#include "fmt/os.h"
#include "gmock/gmock.h"
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \

View File

@@ -9,6 +9,7 @@ add_library(gtest STATIC
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
target_include_directories(gtest SYSTEM PUBLIC .)
target_compile_features(gtest PUBLIC cxx_std_11)
find_package(Threads)
if (Threads_FOUND)
@@ -17,13 +18,6 @@ else ()
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
endif ()
# Workaround GTest bug https://github.com/google/googletest/issues/705.
fmt_check_cxx_compiler_flag(
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks)
endif ()
if (MSVC)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)

View File

@@ -13778,7 +13778,7 @@ UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith(
UntypedActionResultHolderBase* result = nullptr;
auto perform_action = [&] {
auto perform_action = [&, this] {
return untyped_action == nullptr
? this->UntypedPerformDefaultAction(untyped_args, ss.str())
: this->UntypedPerformAction(untyped_action, untyped_args);

View File

@@ -20,8 +20,8 @@ template <typename T> class mock_allocator {
mock_allocator() {}
mock_allocator(const mock_allocator&) {}
using value_type = T;
MOCK_METHOD1_T(allocate, T*(size_t n));
MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
MOCK_METHOD(T*, allocate, (size_t));
MOCK_METHOD(void, deallocate, (T*, size_t));
};
template <typename Allocator> class allocator_ref {

View File

@@ -15,8 +15,6 @@
# define FMT_HIDE_MODULE_BUGS
#endif
#define FMT_MODULE_TEST
#include <bit>
#include <chrono>
#include <exception>
@@ -41,7 +39,6 @@
#else
# define FMT_POSIX(call) call
#endif
#define FMT_OS_H_ // don't pull in os.h directly or indirectly
import fmt;
@@ -53,13 +50,8 @@ static bool macro_leaked =
false;
#endif
// Include sources to pick up functions and classes from the module rather than
// from the non-modular library which is baked into the 'test-main' library.
// This averts linker problems:
// - strong ownership model: missing linker symbols
// - weak ownership model: duplicate linker symbols
#include "gtest-extra.cc"
#include "util.cc"
#define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly
#include "gtest-extra.h"
// an implicitly exported namespace must be visible [module.interface]/2.2
TEST(module_test, namespace) {
@@ -75,9 +67,8 @@ bool oops_detail_namespace_is_visible;
namespace fmt {
bool namespace_detail_invisible() {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \
(_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704))
// bug in msvc up to 16.11.5 / 17.0-pre5:
_MSC_FULL_VER <= 193700000
// bug in msvc up to at least 17.7:
// the namespace is visible even when it is neither
// implicitly nor explicitly exported
@@ -111,7 +102,7 @@ TEST(module_test, macros) {
// but rather visibility of all client-facing overloads, reachability of
// non-exported entities, name lookup and overload resolution within
// template instantitions.
// Excercise all exported entities of the API at least once.
// Exercise all exported entities of the API at least once.
// Instantiate as many code paths as possible.
TEST(module_test, to_string) {
@@ -140,7 +131,7 @@ TEST(module_test, format_to) {
EXPECT_EQ("42", std::string_view(buffer));
fmt::memory_buffer mb;
fmt::format_to(mb, "{}", 42);
fmt::format_to(std::back_inserter(mb), "{}", 42);
EXPECT_EQ("42", std::string_view(buffer));
std::wstring w;
@@ -152,7 +143,7 @@ TEST(module_test, format_to) {
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
fmt::wmemory_buffer wb;
fmt::format_to(wb, L"{}", 42);
fmt::format_to(std::back_inserter(wb), L"{}", 42);
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
}
@@ -211,8 +202,8 @@ TEST(module_test, dynamic_format_args) {
TEST(module_test, vformat) {
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"),
fmt::make_wformat_args(42)));
EXPECT_EQ(L"42",
fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42)));
}
TEST(module_test, vformat_to) {
@@ -245,9 +236,9 @@ TEST(module_test, vformat_to_n) {
auto wstore = fmt::make_wformat_args(12345);
std::wstring w;
auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1,
fmt::to_string_view(L"{}"), wstore);
fmt::wstring_view(L"{}"), wstore);
wchar_t wbuffer[4] = {0};
fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore);
fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore);
}
std::string as_string(std::wstring_view text) {
@@ -258,22 +249,18 @@ std::string as_string(std::wstring_view text) {
TEST(module_test, print) {
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ");
if (false) {
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
}
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4."));
}
TEST(module_test, vprint) {
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
"4.2");
if (false) {
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
as_string(L"42"));
}
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
as_string(L"42"));
}
TEST(module_test, named_args) {
@@ -284,9 +271,7 @@ TEST(module_test, named_args) {
TEST(module_test, literals) {
using namespace fmt::literals;
EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42));
EXPECT_EQ("42", "{}"_format(42));
EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42));
EXPECT_EQ(L"42", L"{}"_format(42));
}
TEST(module_test, locale) {
@@ -320,7 +305,7 @@ TEST(module_test, string_view) {
TEST(module_test, memory_buffer) {
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
fmt::format_to(buffer, "{}", "42");
fmt::format_to(std::back_inserter(buffer), "{}", "42");
EXPECT_EQ("42", to_string(buffer));
fmt::memory_buffer nbuffer(std::move(buffer));
EXPECT_EQ("42", to_string(nbuffer));
@@ -395,27 +380,20 @@ struct test_formatter : fmt::formatter<char> {
bool check() { return true; }
};
struct test_dynamic_formatter : fmt::dynamic_formatter<> {
bool check() { return true; }
};
TEST(module_test, formatter) {
EXPECT_TRUE(test_formatter{}.check());
EXPECT_TRUE(test_dynamic_formatter{}.check());
}
TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); }
TEST(module_test, join) {
int arr[3] = {1, 2, 3};
std::vector<double> vec{1.0, 2.0, 3.0};
std::initializer_list<int> il{1, 2, 3};
auto sep = fmt::to_string_view(", ");
auto sep = fmt::string_view(", ");
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep)));
auto wsep = fmt::to_string_view(L", ");
auto wsep = fmt::wstring_view(L", ");
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep)));
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep)));
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep)));
@@ -426,7 +404,6 @@ TEST(module_test, time) {
EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120);
EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120);
auto chrono_now = std::chrono::system_clock::now();
EXPECT_TRUE(fmt::localtime(chrono_now).tm_year > 120);
EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120);
}
@@ -453,34 +430,16 @@ TEST(module_test, weekday) {
EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
}
TEST(module_test, to_string_view) {
using fmt::to_string_view;
fmt::string_view nsv{to_string_view("42")};
EXPECT_EQ("42", nsv);
fmt::wstring_view wsv{to_string_view(L"42")};
EXPECT_EQ(L"42", wsv);
}
TEST(module_test, printf) {
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42");
if (false) {
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456),
as_string(L"42.123456"));
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
}
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), as_string(L"42.123456"));
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
}
TEST(module_test, fprintf) {
EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42");
std::ostringstream os;
fmt::fprintf(os, "%s", "bla");
EXPECT_EQ("bla", os.str());
EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42"));
std::wostringstream ws;
fmt::fprintf(ws, L"%s", L"bla");
EXPECT_EQ(L"bla", ws.str());
}
TEST(module_test, sprintf) {
@@ -490,25 +449,15 @@ TEST(module_test, sprintf) {
TEST(module_test, vprintf) {
EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42");
if (false) {
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
as_string(L"42"));
}
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
as_string(L"42"));
}
TEST(module_test, vfprintf) {
auto args = fmt::make_printf_args(42);
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42");
std::ostringstream os;
fmt::vfprintf(os, "%d", args);
EXPECT_EQ("42", os.str());
auto wargs = fmt::make_wprintf_args(42);
if (false) {
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
}
std::wostringstream ws;
fmt::vfprintf(ws, L"%d", wargs);
EXPECT_EQ(L"42", ws.str());
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
}
TEST(module_test, vsprintf) {
@@ -538,8 +487,13 @@ TEST(module_test, buffered_file) {
}
TEST(module_test, output_file) {
#ifdef __clang__
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
"Clang 16.0 emits multiple copies of vtables");
#else
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
out.close();
#endif
}
struct custom_context {
@@ -552,21 +506,15 @@ TEST(module_test, custom_context) {
EXPECT_TRUE(!custom_arg);
}
struct disabled_formatter {};
TEST(module_test, has_formatter) {
EXPECT_FALSE(
(fmt::has_formatter<disabled_formatter, fmt::format_context>::value));
}
TEST(module_test, is_formattable) {
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
}
TEST(module_test, compile_format_string) {
using namespace fmt::literals;
#ifdef __clang__
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
"Clang 16.0 fails to import user-defined literals");
#else
EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42));
EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42));
EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2));
EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42"));
#endif
}

View File

@@ -22,60 +22,6 @@ using wstring_view = fmt::basic_string_view<wchar_t>;
# include <windows.h>
TEST(util_test, utf16_to_utf8) {
auto s = std::string("ёжик");
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}
TEST(util_test, utf16_to_utf8_empty_string) {
std::string s = "";
fmt::detail::utf16_to_utf8 u(L"");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}
template <typename Converter, typename Char>
void check_utf_conversion_error(const char* message,
fmt::basic_string_view<Char> str =
fmt::basic_string_view<Char>(nullptr, 1)) {
fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
auto error = std::system_error(std::error_code());
try {
(Converter)(str);
} catch (const std::system_error& e) {
error = e;
}
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value());
EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out)));
}
TEST(util_test, utf16_to_utf8_error) {
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(util_test, utf16_to_utf8_convert) {
fmt::detail::utf16_to_utf8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1)));
EXPECT_EQ(ERROR_INVALID_PARAMETER,
u.convert(wstring_view(L"foo", INT_MAX + 1u)));
}
TEST(os_test, format_std_error_code) {
EXPECT_EQ("generic:42",
fmt::format(FMT_STRING("{0}"),
std::error_code(42, std::generic_category())));
EXPECT_EQ("system:42",
fmt::format(FMT_STRING("{0}"),
std::error_code(42, fmt::system_category())));
EXPECT_EQ("system:-42",
fmt::format(FMT_STRING("{0}"),
std::error_code(-42, fmt::system_category())));
}
TEST(os_test, format_windows_error) {
LPWSTR message = nullptr;
auto result = FormatMessageW(
@@ -83,7 +29,8 @@ TEST(os_test, format_windows_error) {
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, nullptr);
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
fmt::detail::unicode_to_utf8<wchar_t> utf8_message(
wstring_view(message, result - 2));
LocalFree(message);
fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
@@ -108,7 +55,8 @@ TEST(os_test, format_long_windows_error) {
LocalFree(message);
return;
}
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
fmt::detail::unicode_to_utf8<wchar_t> utf8_message(
wstring_view(message, result - 2));
LocalFree(message);
fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
@@ -139,6 +87,17 @@ TEST(os_test, report_windows_error) {
fmt::to_string(out));
}
# if FMT_USE_FCNTL && !defined(__MINGW32__)
TEST(file_test, open_windows_file) {
using fmt::file;
file out = file::open_windows_file(L"test-file",
file::WRONLY | file::CREATE | file::TRUNC);
out.write("x", 1);
file in = file::open_windows_file(L"test-file", file::RDONLY);
EXPECT_READ(in, "x");
}
# endif // FMT_USE_FCNTL && !defined(__MINGW32__)
#endif // _WIN32
#if FMT_USE_FCNTL

View File

@@ -86,13 +86,13 @@ TEST(ostream_test, format_specs) {
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
format_error, "format specifier requires numeric argument");
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
@@ -106,9 +106,17 @@ TEST(ostream_test, empty_custom_output) {
}
TEST(ostream_test, print) {
std::ostringstream os;
fmt::print(os, "Don't {}!", "panic");
EXPECT_EQ("Don't panic!", os.str());
{
std::ostringstream os;
fmt::print(os, "Don't {}!", "panic");
EXPECT_EQ("Don't panic!", os.str());
}
{
std::ostringstream os;
fmt::println(os, "Don't {}!", "panic");
EXPECT_EQ("Don't panic!\n", os.str());
}
}
TEST(ostream_test, write_to_ostream) {
@@ -132,7 +140,7 @@ TEST(ostream_test, write_to_ostream_max_size) {
} buffer(max_size);
struct mock_streambuf : std::streambuf {
MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n));
MOCK_METHOD(std::streamsize, xsputn, (const void*, std::streamsize));
auto xsputn(const char* s, std::streamsize n) -> std::streamsize override {
const void* v = s;
return xsputn(v, n);
@@ -218,45 +226,6 @@ TEST(ostream_test, format_to_n) {
EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
}
template <typename T> struct convertible {
T value;
explicit convertible(const T& val) : value(val) {}
operator T() const { return value; }
};
TEST(ostream_test, disable_builtin_ostream_operators) {
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
}
struct streamable_and_convertible_to_bool {
operator bool() const { return true; }
};
std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
return os << "foo";
}
TEST(ostream_test, format_convertible_to_bool) {
// operator<< is intentionally not used because of potential ODR violations.
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true");
}
struct streamable_and_convertible_to_string_view {
operator fmt::string_view() const { return "foo"; }
};
std::ostream& operator<<(std::ostream& os,
streamable_and_convertible_to_string_view) {
return os << "bar";
}
TEST(ostream_test, format_convertible_to_string_vew) {
// operator<< is intentionally not used because of potential ODR violations.
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_string_view()),
"foo");
}
struct copyfmt_test {};
std::ostream& operator<<(std::ostream& os, copyfmt_test) {

View File

@@ -72,12 +72,6 @@ int test::open(const char* path, int oflag, int mode) {
EMULATE_EINTR(open, -1);
return ::open(path, oflag, mode);
}
#else
errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag,
int pmode) {
EMULATE_EINTR(open, EINTR);
return _sopen_s(pfh, filename, oflag, shflag, pmode);
}
#endif
#ifndef _WIN32
@@ -220,11 +214,11 @@ TEST(os_test, getpagesize) {
}
TEST(file_test, open_retry) {
# ifndef _WIN32
write_file("temp", "there must be something here");
std::unique_ptr<file> f{nullptr};
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
"cannot open file temp");
# ifndef _WIN32
char c = 0;
f->read(&c, 1);
# endif

View File

@@ -37,8 +37,6 @@ int fstat(int fd, struct stat* buf);
#else
typedef unsigned size_t;
typedef int ssize_t;
errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag,
int pmode);
#endif
#ifndef _WIN32

View File

@@ -225,10 +225,8 @@ TEST(printf_test, hash_flag) {
EXPECT_PRINTF("-42.0000", "%#g", -42.0);
EXPECT_PRINTF("-42.0000", "%#G", -42.0);
safe_sprintf(buffer, "%#a", 16.0);
EXPECT_PRINTF(buffer, "%#a", 16.0);
safe_sprintf(buffer, "%#A", 16.0);
EXPECT_PRINTF(buffer, "%#A", 16.0);
EXPECT_PRINTF("0x1.p+4", "%#a", 16.0);
EXPECT_PRINTF("0X1.P+4", "%#A", 16.0);
// '#' flag is ignored for non-numeric types.
EXPECT_PRINTF("x", "%#c", 'x');
@@ -239,7 +237,7 @@ TEST(printf_test, width) {
// Width cannot be specified twice.
EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
"invalid type specifier");
"invalid format specifier");
EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
"number is too big");
@@ -407,10 +405,7 @@ TEST(printf_test, length) {
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
}
TEST(printf_test, bool) {
EXPECT_PRINTF("1", "%d", true);
EXPECT_PRINTF("true", "%s", true);
}
TEST(printf_test, bool) { EXPECT_PRINTF("1", "%d", true); }
TEST(printf_test, int) {
EXPECT_PRINTF("-42", "%d", -42);

View File

@@ -12,6 +12,8 @@
#include "fmt/ranges.h"
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <vector>
@@ -50,6 +52,14 @@ TEST(ranges_test, format_vector) {
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
auto vc = std::vector<char>{'a', 'b', 'c'};
auto vvc = std::vector<std::vector<char>>{vc, vc};
EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']");
EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]");
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
}
TEST(ranges_test, format_vector2) {
@@ -96,6 +106,7 @@ TEST(ranges_test, format_tuple) {
auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
@@ -108,6 +119,25 @@ TEST(ranges_test, format_tuple) {
EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
}
struct not_default_formattable {};
struct bad_format {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<not_default_formattable> {
auto parse(format_parse_context&) -> const char* { throw bad_format(); }
auto format(not_default_formattable, format_context& ctx)
-> format_context::iterator {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(ranges_test, tuple_parse_calls_element_parse) {
auto f = fmt::formatter<std::tuple<not_default_formattable>>();
auto ctx = fmt::format_parse_context("");
EXPECT_THROW(f.parse(ctx), bad_format);
}
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
struct tuple_like {
int i;
@@ -149,32 +179,24 @@ TEST(ranges_test, format_to) {
EXPECT_STREQ(buf, "[1, 2, 3]");
}
struct path_like {
template <typename Char> struct path_like {
const path_like* begin() const;
const path_like* end() const;
operator std::string() const;
operator std::basic_string<Char>() const;
};
TEST(ranges_test, path_like) {
EXPECT_FALSE((fmt::is_range<path_like, char>::value));
TEST(ranges_test, disabled_range_formatting_of_path) {
// Range formatting of path is disabled because of infinite recursion
// (path element is itself a path).
EXPECT_EQ((fmt::range_format_kind<path_like<char>, char>::value),
fmt::range_format::disabled);
EXPECT_EQ((fmt::range_format_kind<path_like<wchar_t>, char>::value),
fmt::range_format::disabled);
}
#ifdef FMT_USE_STRING_VIEW
struct string_like {
const char* begin();
const char* end();
operator fmt::string_view() const { return "foo"; }
operator std::string_view() const { return "foo"; }
};
TEST(ranges_test, format_string_like) {
EXPECT_EQ(fmt::format("{}", string_like()), "foo");
}
#endif // FMT_USE_STRING_VIEW
// A range that provides non-const only begin()/end() to test fmt::join handles
// that.
// A range that provides non-const only begin()/end() to test fmt::join
// handles that.
//
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
// information during iteration so they only provide non-const begin()/end().
@@ -198,7 +220,7 @@ template <typename T> class noncopyable_range {
std::vector<T> vec;
public:
using const_iterator = typename ::std::vector<T>::const_iterator;
using iterator = typename ::std::vector<T>::iterator;
template <typename... Args>
explicit noncopyable_range(Args&&... args)
@@ -207,8 +229,8 @@ template <typename T> class noncopyable_range {
noncopyable_range(noncopyable_range const&) = delete;
noncopyable_range(noncopyable_range&) = delete;
const_iterator begin() const { return vec.begin(); }
const_iterator end() const { return vec.end(); }
iterator begin() { return vec.begin(); }
iterator end() { return vec.end(); }
};
TEST(ranges_test, range) {
@@ -237,7 +259,6 @@ TEST(ranges_test, enum_range) {
}
#if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value));
@@ -369,7 +390,7 @@ TEST(ranges_test, is_printable) {
EXPECT_FALSE(is_printable(0x110000));
}
TEST(ranges_test, escape_string) {
TEST(ranges_test, escape) {
using vec = std::vector<std::string>;
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
@@ -389,20 +410,13 @@ TEST(ranges_test, escape_string) {
"[\"\\xf0(\\x00\\x00anything\"]");
// Correct utf-8.
EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]");
EXPECT_EQ(fmt::format("{}", vec{"🦄"}), "[\"🦄\"]");
}
}
#ifdef FMT_USE_STRING_VIEW
struct convertible_to_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(ranges_test, escape_convertible_to_string_view) {
EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)),
"[\"foo\"]");
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
"[['x']]");
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
}
#endif // FMT_USE_STRING_VIEW
template <typename R> struct fmt_ref_view {
R* r;
@@ -422,3 +436,62 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
TEST(ranges_test, vector_char) {
EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
}
TEST(ranges_test, container_adaptor) {
{
using fmt::detail::is_container_adaptor_like;
using T = std::nullptr_t;
static_assert(is_container_adaptor_like<std::stack<T>>::value, "");
static_assert(is_container_adaptor_like<std::queue<T>>::value, "");
static_assert(is_container_adaptor_like<std::priority_queue<T>>::value, "");
static_assert(!is_container_adaptor_like<std::vector<T>>::value, "");
}
{
auto s = std::stack<int>();
s.push(1);
s.push(2);
EXPECT_EQ(fmt::format("{}", s), "[1, 2]");
EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]");
}
{
auto q = std::queue<int>();
q.push(1);
q.push(2);
EXPECT_EQ(fmt::format("{}", q), "[1, 2]");
}
{
auto q = std::priority_queue<int>();
q.push(3);
q.push(1);
q.push(2);
q.push(4);
EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]");
}
{
auto s = std::stack<char, std::string>();
s.push('a');
s.push('b');
// See https://cplusplus.github.io/LWG/issue3881.
EXPECT_EQ(fmt::format("{}", s), "['a', 'b']");
}
{
struct my_container_adaptor {
using value_type = int;
using container_type = std::vector<value_type>;
void push(const value_type& v) { c.push_back(v); }
protected:
container_type c;
};
auto m = my_container_adaptor();
m.push(1);
m.push(2);
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
}
}

View File

@@ -65,7 +65,7 @@ TEST(scan_test, read_string_view) {
EXPECT_EQ(s, "foo");
}
#ifndef _WIN32
#ifdef FMT_HAVE_STRPTIME
namespace fmt {
template <> struct scanner<tm> {
std::string format;

View File

@@ -42,7 +42,7 @@ struct scan_context {
public:
using iterator = const char*;
explicit scan_context(string_view input) : input_(input) {}
explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {}
iterator begin() const { return input_.data(); }
iterator end() const { return begin() + input_.size(); }
@@ -83,17 +83,21 @@ class scan_arg {
// TODO: more types
};
scan_arg() : type(scan_type::none_type) {}
scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {}
scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {}
scan_arg(long long& value)
FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {}
FMT_CONSTEXPR scan_arg(int& value)
: type(scan_type::int_type), int_value(&value) {}
FMT_CONSTEXPR scan_arg(unsigned& value)
: type(scan_type::uint_type), uint_value(&value) {}
FMT_CONSTEXPR scan_arg(long long& value)
: type(scan_type::long_long_type), long_long_value(&value) {}
scan_arg(unsigned long long& value)
FMT_CONSTEXPR scan_arg(unsigned long long& value)
: type(scan_type::ulong_long_type), ulong_long_value(&value) {}
scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {}
scan_arg(fmt::string_view& value)
FMT_CONSTEXPR scan_arg(std::string& value)
: type(scan_type::string_type), string(&value) {}
FMT_CONSTEXPR scan_arg(fmt::string_view& value)
: type(scan_type::string_view_type), string_view(&value) {}
template <typename T> scan_arg(T& value) : type(scan_type::custom_type) {
template <typename T>
FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) {
custom.value = &value;
custom.scan = scan_custom_arg<T>;
}
@@ -114,7 +118,7 @@ struct scan_args {
const detail::scan_arg* data;
template <size_t N>
scan_args(const std::array<detail::scan_arg, N>& store)
FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store)
: size(N), data(store.data()) {
static_assert(N < INT_MAX, "too many arguments");
}
@@ -154,7 +158,8 @@ struct scan_handler : error_handler {
}
public:
scan_handler(string_view format, string_view input, scan_args args)
FMT_CONSTEXPR scan_handler(string_view format, string_view input,
scan_args args)
: parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
const char* pos() const { return scan_ctx_.begin(); }

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.25)
project(fmt-link CXX)

View File

@@ -6,47 +6,108 @@
// For the license information refer to format.h.
#include "fmt/std.h"
#include "fmt/ranges.h"
#include <stdexcept>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith
using testing::StartsWith;
TEST(std_test, path) {
#ifdef __cpp_lib_filesystem
TEST(std_test, path) {
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")),
"\"foo\\\"bar.txt\"");
EXPECT_EQ(fmt::format("{:?}", std::filesystem::path("foo\"bar.txt")),
"\"foo\\\"bar.txt\"");
# ifdef _WIN32
// File.txt in Russian.
const wchar_t unicode_path[] = {0x424, 0x430, 0x439, 0x43b, 0x2e,
0x74, 0x78, 0x74, 0};
const char unicode_u8path[] = {'"', char(0xd0), char(0xa4), char(0xd0),
char(0xb0), char(0xd0), char(0xb9), char(0xd0),
char(0xbb), '.', 't', 'x',
't', '"', '\0'};
EXPECT_EQ(fmt::format("{}", std::filesystem::path(unicode_path)),
unicode_u8path);
EXPECT_EQ(fmt::format("{}", std::filesystem::path(
L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"\"Шчучыншчына\"");
EXPECT_EQ(fmt::format("{}", std::filesystem::path(L"\xd800")), "\"\\ud800\"");
# endif
#endif
}
TEST(ranges_std_test, format_vector_path) {
// Test ambiguity problem described in #2954.
#ifdef __cpp_lib_filesystem
TEST(ranges_std_test, format_vector_path) {
auto p = std::filesystem::path("foo/bar.txt");
auto c = std::vector<std::string>{"abc", "def"};
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
"path=\"foo/bar.txt\", range=[\"abc\", \"def\"]");
#endif
}
// Test that path is not escaped twice in the debug mode.
TEST(ranges_std_test, format_quote_path) {
auto vec =
std::vector<std::filesystem::path>{"path1/file1.txt", "path2/file2.txt"};
EXPECT_EQ(fmt::format("{}", vec),
"[\"path1/file1.txt\", \"path2/file2.txt\"]");
# ifdef __cpp_lib_optional
auto o = std::optional<std::filesystem::path>("path/file.txt");
EXPECT_EQ(fmt::format("{}", o), "optional(\"path/file.txt\")");
EXPECT_EQ(fmt::format("{:?}", o), "optional(\"path/file.txt\")");
# endif
}
#endif
TEST(std_test, thread_id) {
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
}
TEST(std_test, optional) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")");
EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2},
std::optional{3}}),
"[optional(1), optional(2), optional(3)]");
EXPECT_EQ(
fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}),
"optional(optional(\"nested\"))");
EXPECT_EQ(
fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30),
"optional(\"left aligned\" )");
EXPECT_EQ(
fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}),
"optional([104, 101, 108, 108, 111])");
EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}),
"optional(\"string\")");
EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')");
EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)");
struct unformattable {};
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
#endif
}
struct throws_on_move {
throws_on_move() = default;
[[noreturn]] throws_on_move(throws_on_move&&) {
throw std::runtime_error("Thrown by throws_on_move");
}
throws_on_move(const throws_on_move&) = default;
};
namespace fmt {
template <> struct formatter<throws_on_move> : formatter<string_view> {
auto format(const throws_on_move&, format_context& ctx) const
-> decltype(ctx.out()) {
string_view str("<throws_on_move>");
return formatter<string_view>::format(str, ctx);
}
};
} // namespace fmt
TEST(std_test, variant) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
@@ -75,5 +136,86 @@ TEST(std_test, variant) {
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
volatile int i = 42; // Test compile error before GCC 11 described in #3068.
EXPECT_EQ(fmt::format("{}", i), "42");
std::variant<std::monostate, throws_on_move> v6;
try {
throws_on_move thrower;
v6.emplace<throws_on_move>(std::move(thrower));
} catch (const std::runtime_error&) {
}
// v6 is now valueless by exception
EXPECT_EQ(fmt::format("{}", v6), "variant(valueless by exception)");
#endif
}
TEST(std_test, error_code) {
EXPECT_EQ("generic:42",
fmt::format(FMT_STRING("{0}"),
std::error_code(42, std::generic_category())));
EXPECT_EQ("system:42",
fmt::format(FMT_STRING("{0}"),
std::error_code(42, fmt::system_category())));
EXPECT_EQ("system:-42",
fmt::format(FMT_STRING("{0}"),
std::error_code(-42, fmt::system_category())));
}
template <typename Catch> void exception_test() {
try {
throw std::runtime_error("Test Exception");
} catch (const Catch& ex) {
EXPECT_EQ("Test Exception", fmt::format("{}", ex));
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex));
}
}
namespace my_ns1 {
namespace my_ns2 {
struct my_exception : public std::exception {
private:
std::string msg;
public:
my_exception(const std::string& s) : msg(s) {}
const char* what() const noexcept override;
};
const char* my_exception::what() const noexcept { return msg.c_str(); }
} // namespace my_ns2
} // namespace my_ns1
TEST(std_test, exception) {
exception_test<std::exception>();
exception_test<std::runtime_error>();
try {
using namespace my_ns1::my_ns2;
throw my_exception("My Exception");
} catch (const std::exception& ex) {
EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception",
fmt::format("{:t}", ex));
EXPECT_EQ("My Exception", fmt::format("{:}", ex));
}
try {
throw std::system_error(std::error_code(), "message");
} catch (const std::system_error& ex) {
EXPECT_THAT(fmt::format("{:t}", ex), StartsWith("std::system_error: "));
}
#ifdef __cpp_lib_filesystem
// Tests that the inline namespace is stripped out, e.g.
// std::filesystem::__cxx11::* -> std::filesystem::*.
try {
throw std::filesystem::filesystem_error("message", std::error_code());
} catch (const std::filesystem::filesystem_error& ex) {
EXPECT_THAT(fmt::format("{:t}", ex),
StartsWith("std::filesystem::filesystem_error: "));
}
#endif
}

View File

@@ -10,11 +10,7 @@
#include <locale>
#include <string>
#ifdef FMT_MODULE_TEST
import fmt;
#else
# include "fmt/os.h"
#endif // FMT_MODULE_TEST
#include "fmt/os.h"
#ifdef _MSC_VER
# define FMT_VSNPRINTF vsprintf_s
@@ -81,5 +77,5 @@ class date {
int day() const { return day_; }
};
// Returns a locale with the given name if available or classic locale othewise.
// Returns a locale with the given name if available or classic locale otherwise.
std::locale get_locale(const char* name, const char* alt_name = nullptr);

View File

@@ -7,6 +7,7 @@
#include "fmt/xchar.h"
#include <algorithm>
#include <complex>
#include <cwchar>
#include <vector>
@@ -15,31 +16,21 @@
#include "fmt/color.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include "fmt/std.h"
#include "gtest-extra.h" // Contains
#include "util.h" // get_locale
using fmt::detail::max_value;
using testing::Contains;
namespace test_ns {
template <typename Char> class test_string {
private:
std::basic_string<Char> s_;
public:
test_string(const Char* s) : s_(s) {}
const Char* data() const { return s_.data(); }
size_t length() const { return s_.size(); }
operator const Char*() const { return s_.c_str(); }
};
template <typename Char>
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
return {s.data(), s.length()};
}
#if defined(__MINGW32__) && !defined(_UCRT)
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
# define FMT_HAS_C99_STRFTIME 0
#else
# define FMT_HAS_C99_STRFTIME 1
#endif
struct non_string {};
} // namespace test_ns
template <typename T> class is_string_test : public testing::Test {};
@@ -61,8 +52,7 @@ TYPED_TEST(is_string_test, is_string) {
using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
fmt::detail::is_string<fmt_string_view>::value);
EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value);
EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value);
EXPECT_FALSE(fmt::detail::is_string<non_string>::value);
}
// std::is_constructible is broken in MSVC until version 2015.
@@ -112,10 +102,12 @@ struct custom_char {
template <typename T>
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
operator int() const { return value; }
operator char() const {
return value <= 0xff ? static_cast<char>(value) : '\0';
}
};
int to_ascii(custom_char c) { return c; }
auto to_ascii(custom_char c) -> char { return c; }
FMT_BEGIN_NAMESPACE
template <> struct is_char<custom_char> : std::true_type {};
@@ -200,7 +192,10 @@ TEST(xchar_test, named_arg_udl) {
TEST(xchar_test, print) {
// Check that the wide print overload compiles.
if (fmt::detail::const_check(false)) fmt::print(L"test");
if (fmt::detail::const_check(false)) {
fmt::print(L"test");
fmt::println(L"test");
}
}
TEST(xchar_test, join) {
@@ -284,7 +279,8 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
}
TEST(chrono_test_wchar, time_point) {
auto t1 = std::chrono::system_clock::now();
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
@@ -292,19 +288,36 @@ TEST(chrono_test_wchar, time_point) {
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
#ifndef _WIN32
// Disabled on Windows, because these formats is not consistent among
// platforms.
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
#elif defined(__MINGW32__) && !defined(_UCRT)
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p", L"%Z"};
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
#endif
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
// Timezone formatters tests makes sense for localtime.
#if FMT_HAS_C99_STRFTIME
spec_list = {L"%z", L"%Z"};
#else
spec_list = {L"%Z"};
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
@@ -312,8 +325,38 @@ TEST(chrono_test_wchar, time_point) {
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
if (spec == L"%z") {
sys_output.insert(sys_output.end() - 2, 1, L':');
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
}
}
// Separate tests for UTC, since std::time_put can use local time and ignoring
// the timezone in std::tm (if it presents on platform).
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
}
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
}
}
@@ -324,9 +367,17 @@ TEST(xchar_test, color) {
TEST(xchar_test, ostream) {
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
std::wostringstream wos;
fmt::print(wos, L"Don't {}!", L"panic");
EXPECT_EQ(wos.str(), L"Don't panic!");
{
std::wostringstream wos;
fmt::print(wos, L"Don't {}!", L"panic");
EXPECT_EQ(wos.str(), L"Don't panic!");
}
{
std::wostringstream wos;
fmt::println(wos, L"Don't {}!", L"panic");
EXPECT_EQ(wos.str(), L"Don't panic!\n");
}
#endif
}
@@ -344,6 +395,7 @@ TEST(xchar_test, escape_string) {
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Char> struct numpunct : std::numpunct<Char> {
protected:
Char do_decimal_point() const override { return '?'; }
@@ -379,6 +431,9 @@ TEST(locale_test, localized_double) {
EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230");
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 0.1), " 0?100000");
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1.0), " 1?000000");
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1e3), " 1~000?000000");
}
TEST(locale_test, format) {
@@ -441,16 +496,16 @@ TEST(locale_test, wformat) {
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
}
TEST(locale_test, double_formatter) {
TEST(locale_test, int_formatter) {
auto loc = std::locale(std::locale(), new special_grouping<char>());
auto f = fmt::formatter<int>();
auto parse_ctx = fmt::format_parse_context("L");
f.parse(parse_ctx);
char buf[10] = {};
fmt::basic_format_context<char*, char> format_ctx(
buf, {}, fmt::detail::locale_ref(loc));
*f.format(12345, format_ctx) = 0;
EXPECT_STREQ("12,345", buf);
auto buf = fmt::memory_buffer();
fmt::basic_format_context<fmt::appender, char> format_ctx(
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
f.format(12345, format_ctx);
EXPECT_EQ(fmt::to_string(buf), "12,345");
}
FMT_BEGIN_NAMESPACE
@@ -461,13 +516,10 @@ template <class charT> struct formatter<std::complex<double>, charT> {
public:
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
basic_format_parse_context<charT>& ctx) {
using handler_type =
detail::dynamic_specs_handler<basic_format_parse_context<charT>>;
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
detail::type::string_type);
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
detail::parse_float_type_spec(specs_, ctx.error_handler());
return it;
auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type::float_type);
detail::parse_float_type_spec(specs_, detail::error_handler());
return end;
}
template <class FormatContext>
@@ -518,4 +570,12 @@ TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
}
TEST(std_test_xchar, optional) {
# ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}),
L"optional(\"wide string\")");
# endif
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR