Compare commits

...

716 Commits

Author SHA1 Message Date
Victor Zverovich
a6e871e39b Don't pull in locale dependency 2025-12-04 06:28:37 -08:00
Watal M. Iwasaki
e137db699a
Fix doc CSS to display white-space properly (#4622)
See fmtlib/fmt.dev#25
2025-12-03 11:35:04 -08:00
dependabot[bot]
d6712ff2c0
Bump actions/checkout from 5.0.0 to 6.0.0 (#4621)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...1af3b93b68)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 09:04:20 -08:00
Victor Zverovich
2d839bbc61 Fix format_to_n 2025-11-30 07:12:48 -08:00
Victor Zverovich
5bc56e24a9 Update clang-format to version 21 2025-11-28 20:05:18 -08:00
bigmoonbit
2727215c11
chore: minor improvement for docs (#4616)
Signed-off-by: bigmoonbit <bigmoonbit@outlook.com>
2025-11-28 12:55:25 -08:00
Victor Zverovich
790b9389ae
Update README for clarity on performance comparison 2025-11-23 11:15:23 -08:00
Victor Zverovich
a731c73fd5 Make FMT_USE_FULL_CACHE_DRAGONBOX depend on __OPTIMIZE_SIZE__ by default
Thanks to Matthias Kretz for the idea.
2025-11-23 09:03:02 -08:00
Victor Zverovich
3391f9e992 Cleanup test 2025-11-22 19:14:32 -08:00
friedkeenan
9f197b22ae
Make FMT_STRING redundant when FMT_USE_CONSTEVAL is enabled (#4612) 2025-11-22 14:36:29 -08:00
Victor Zverovich
f20b16617e Fix formatting 2025-11-22 13:48:03 -08:00
Victor Zverovich
14451704d5 Opt out std::complex from tuple formatting 2025-11-22 08:23:11 -08:00
Victor Zverovich
7a0da1c68a Merge compile-time FP tests into other compile-time tests 2025-11-22 07:46:38 -08:00
Victor Zverovich
e3d2174b3c Remove more invalid tests 2025-11-22 07:30:11 -08:00
Victor Zverovich
a6fb4d3b06 Remove invalid tests 2025-11-22 07:00:06 -08:00
Victor Zverovich
706fecea30 Apply clang-format 2025-11-22 06:45:50 -08:00
Victor Zverovich
e00fd756cc
Update link 2025-11-19 06:58:44 -08:00
Victor Zverovich
fc17e825d9 Remove the broken fmt::say function 2025-11-12 11:06:01 -08:00
Victor Zverovich
3fccfb8a80
Update benchmark table formatting in README 2025-11-07 10:05:56 -08:00
Victor Zverovich
690c9c71a0
Update benchmark results 2025-11-06 17:25:24 -08:00
Victor Zverovich
1122268510 Make path formatting lossless with WTF-8 2025-11-05 10:59:06 -10:00
Victor Zverovich
a4c7e17133 Improve build speed, take 2 2025-11-04 10:19:39 -10:00
Victor Zverovich
bfd0129b91 Improve build speed 2025-11-04 09:33:18 -10:00
Victor Zverovich
62f57b2496 Fix the macOS build 2025-11-04 09:12:57 -10:00
Victor Zverovich
ef7a566413 Reduce bloat-test result in debug mode from ~200k to ~85k 2025-11-03 16:21:10 -10:00
Victor Zverovich
42840cb415
Update benchmark results 2025-11-03 16:50:43 -08:00
Victor Zverovich
5ac44cd128 Fix a warning 2025-11-03 13:39:29 -10:00
Victor Zverovich
23c13b3060 Handle ulink in docs 2025-11-03 13:14:48 -10:00
Victor Zverovich
29c46fb82d Add support for more doxygen tags 2025-11-03 10:57:41 -10:00
Victor Zverovich
a0571f3f59 Document output_file 2025-11-03 10:11:31 -10:00
dependabot[bot]
a195dd6b37
Bump github/codeql-action from 3.30.5 to 4.31.2 (#4599)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.5 to 4.31.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3599b3baa1...0499de31b9)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 08:53:27 -08:00
Victor Zverovich
33ad559eb8 Fix fuzzer 2025-11-02 07:52:16 -10:00
dependabot[bot]
b6cd356196
Bump actions/upload-artifact from 4.6.0 to 5.0.0 (#4598)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.0 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65c4c4a1dd...330a01c490)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 09:39:15 -07:00
J. Berger
c3be070b7e
When using MSVC x86 to compile v12.0.0 or v12.1.0, conversions from __int64 to a 32bit unsigned int trigger warnings. (#4594)
This is a follow-up for PR #4572.
2025-11-01 09:38:30 -07:00
Stéén
27bf8b47fe
Add FMT_CONSTEXPR to static_format_result members (#4591)
Co-authored-by: Robin Oger <roger@qcify.com>
2025-10-29 18:34:42 -07:00
Victor Zverovich
407c905e45 Update version 2025-10-29 07:40:27 -07:00
Victor Zverovich
f781d2b932
Update ChangeLog.md 2025-10-25 09:10:24 -07:00
Victor Zverovich
5987082c47 Bump version 2025-10-25 08:30:50 -07:00
Victor Zverovich
681c9e689b Update changelog 2025-10-25 08:30:50 -07:00
Peter Hill
913507044b
Fix leaky diagnostic ignored pragma (#4588)
Ignoring the `-Wconversion` diagnostic in `make_format_args` was
leaking out of the header, resulting in that warning being ignored in
downstream code that includes `fmt/base.h`.

Instead, we should `push`/`pop` the diagnostics to ensure this is
cleaned up.
2025-10-25 08:28:24 -07:00
Victor Zverovich
ff357e9e4a Remove extra whitespace 2025-10-25 08:10:57 -07:00
Victor Zverovich
728dfeab5b Fix apidoc comment 2025-10-25 08:06:23 -07:00
Victor Zverovich
7adc922ebb Update changelog 2025-10-25 08:02:24 -07:00
Victor Zverovich
70ed0ab82a Update changelog 2025-10-25 07:51:29 -07:00
Victor Zverovich
b95fd6132b Avoid an ABI break for clang 2025-10-25 07:17:41 -07:00
Victor Zverovich
7241bbb149 Update changelog 2025-10-22 15:32:15 -07:00
Victor Zverovich
c0ddbacfd3 Bump version 2025-10-22 15:32:15 -07:00
Victor Zverovich
9395ef5fcb Don't include std::locale::collate value in the symbol 2025-10-22 13:48:13 -07:00
Victor Zverovich
9721d974fc Workaround ABI compatibility between clang and gcc 2025-10-22 12:34:47 -07:00
Fatih BAKIR
d6bdb69c62
Move FMT_API from ostream class to members (#4584)
Putting FMT_API on the class definition propagates it to the base class
detail::buffer<char>'s members. However, MSVC not emit definitions for
inline members unless it sees the symbols as FMT_API when compiling.

This fix removes the FMT_API declaration from the class itself and marks
individual non-inline members as FMT_API to address the issue.

Fixes https://github.com/fmtlib/fmt/issues/4576
2025-10-20 07:49:23 -07:00
Victor Zverovich
08d38d6e78 Make error_code formatter debug-enabled 2025-10-19 10:35:45 -07:00
Victor Zverovich
a2289b8593 Fix the build 2025-10-19 10:30:42 -07:00
Victor Zverovich
e8da5ba275 Fix formatting 2025-10-19 10:22:39 -07:00
Victor Zverovich
e2aa06cd0a Workaround ABI incompatibility between clang ang gcc 2025-10-19 08:40:13 -07:00
Vladislav Shchapov
85f6ecc7a0
Add format_as support for std::variant and std::expected formatters (#4575)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-10-18 14:05:21 -07:00
Harry Denholm
378a5ab3c1
value-initialise the buffer array member of detail::ansi_color_escape so that it can be used in a constexpr context in MSVC; compiler rejects as non-constant due to 'uninitialized symbol' otherwise (#4581)
Co-authored-by: Harry Denholm <ishani@users.noreply.github.com>
2025-10-17 08:10:56 -07:00
John Zimmerman
656d14db8b
docs: format two unescaped printf references with backticks (#4578) 2025-10-16 11:27:51 -07:00
Justin Riddell
31bed1bb65
Fix module fmt::join<string_view> (#4577)
FMT_USE_STRING_VIEW define was incorrectly not being defined when using
modules, so there was no appropriate formatter specialization for
std::string_view
Fixes #4379
2025-10-16 11:25:19 -07:00
LiangHu
8eebb4334b
[fix] #4565 When using MSVC to compile v12.0.0, many compilation warn… (#4572) 2025-10-12 10:56:13 -07:00
Victor Zverovich
beefc1c14f Tweak wording 2025-10-12 10:54:28 -07:00
Victor Zverovich
81fe170849 Update docs 2025-10-12 10:42:12 -07:00
Victor Zverovich
491dc16a6d Cleanup docs 2025-10-12 10:33:05 -07:00
Victor Zverovich
41326207c7 Cleanup docs 2025-10-12 10:20:16 -07:00
Victor Zverovich
c4f70ab69e Cleanup docs 2025-10-12 09:34:46 -07:00
Victor Zverovich
5e214f0c43 Cleanup docs 2025-10-12 09:24:58 -07:00
Victor Zverovich
1234bc312e Apply clang-tidy 2025-10-11 07:58:17 -07:00
rohitsutreja
b77a751625
Revert std::malloc/std::free to global malloc/free (#4569) (#4570) 2025-10-10 09:37:05 -07:00
rohitsutreja
8db24c0ea9
restore 'inline' for normalize_libcxx_inline_namespaces (#4571) 2025-10-10 07:49:53 -07:00
Fatih BAKIR
03c7e28965
write_demangled_name supports libc++ + clang-cl (#4560)
The current implementation assumes whenever we're on an FMT_MSC_VERSION
compiler, the standard library is MSVC's STL. However, with clang-cl we
have the possibility of using LLVM libc++ instead of MSVC STL. In that
scenario, the previous implementation produced the wrong demangled names
for RTTI types.

This patch detects the different combinations, and combines the existing
demangling implementations to produce the correct names and make all
tests pass on libc++ + clang-cl.
2025-10-08 12:23:57 -07:00
Victor Zverovich
47a18b2fe9 Minor cleanup 2025-10-07 13:52:59 -07:00
Peter Steneteg
eb44d87b52
docs: range formatter grammar fix (#4567) 2025-10-07 11:34:00 -07:00
Martin Valgur
b580360ab7
base.h: _BitInt is not available when Clang is used as a host compiler for NVCC (#4564) 2025-10-06 08:45:12 -07:00
Oleksandr Koval
486e7ba579
support std::optional holding cv-qualified types (#4562)
Fixes #4561
2025-10-03 04:40:44 -07:00
Victor Zverovich
27ea09836a Error on unsafe uses of join 2025-10-02 16:24:22 -04:00
Victor Zverovich
b5b9317a3c Warn about unsafe use of join 2025-10-02 12:35:07 -04:00
dependabot[bot]
d13e5d048d
Bump github/codeql-action from 3.29.7 to 3.30.5 (#4558)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.7 to 3.30.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](51f77329af...3599b3baa1)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.30.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 06:08:07 -07:00
teruyamato0731
b9ac8b225d
style: Correct indentation in locale initialization (#4557) 2025-10-02 04:10:31 -07:00
teruyamato0731
4801f54e59
docs: Add compile-time options to API documentation (#4551) 2025-09-30 14:03:11 -07:00
Victor Zverovich
5f66e07cb0 Suppress an unused argument warning 2025-09-28 19:45:41 -04:00
Victor Zverovich
17be91c079 Fix a clang-tidy error 2025-09-26 08:28:51 -07:00
Victor Zverovich
dcea616535 Fix compilation with locale disabled in header-only mode 2025-09-26 07:48:43 -07:00
SnapperTT
a2fd48e039
Make FMT_USE_CONSTEVAL optional #4545 (#4546) 2025-09-24 11:00:41 -07:00
Nikita Tsarev
e28c371c0f
Fix ambiguous call to fprintf when compiling as a C++20 module (#4548) 2025-09-22 11:58:59 -07:00
Victor Zverovich
6b6cdd9405 Store size in a local variable while unchanged 2025-09-21 12:51:06 -07:00
Victor Zverovich
c5e55972ae Minor improvements to mkdocs 2025-09-21 12:47:15 -07:00
Victor Zverovich
dc409ee86d Explain mkdocs deploy invocation 2025-09-21 12:47:09 -07:00
Yuwei Zhao
4cce5f458d
Perf: Optimize function append in include/fmt/base.h (#4541) 2025-09-21 12:40:26 -07:00
Victor Zverovich
aa8a30838a Fix mike invocation 2025-09-21 08:03:21 -07:00
CrackedMatter
b18ece7d71
Export is_compiled_string and operator""_cf (#4544) 2025-09-19 08:54:54 -07:00
Victor Zverovich
e424e3f2e6 Update version 2025-09-17 09:45:35 -07:00
Victor Zverovich
ddd20d57ec Bump version 2025-09-17 09:14:47 -07:00
Victor Zverovich
594143e374
Fix formatting in ChangeLog benchmark section
Correct formatting of the benchmark table in ChangeLog.
2025-09-17 09:08:01 -07:00
Victor Zverovich
09005428ab
Fix formatting in ChangeLog.md 2025-09-17 09:05:36 -07:00
Victor Zverovich
aaefc029d1 Update changelog 2025-09-17 09:05:15 -07:00
Victor Zverovich
9fb9f17dac Update changelog 2025-09-17 09:04:32 -07:00
Victor Zverovich
a3b3d7ed46 Simplify duration cast 2025-09-13 11:32:03 -07:00
Victor Zverovich
9aee518f3e Update changelog 2025-09-13 10:51:11 -07:00
Victor Zverovich
83189189a1 Remove get_dynamic_spec 2025-09-13 10:38:50 -07:00
Victor Zverovich
9bb14ffc47 Remove deprecated APIs 2025-09-13 10:07:16 -07:00
Victor Zverovich
556c4177b6 Remove deprecated localtime 2025-09-13 09:41:24 -07:00
Victor Zverovich
bfdef8b15d Remove deprecated functions 2025-09-13 09:25:53 -07:00
Victor Zverovich
e33c76a1c2 Update changelog 2025-09-13 08:17:13 -07:00
Victor Zverovich
f9face7147 Update changelog 2025-09-13 07:59:25 -07:00
Uilian Ries
36390db094
Add Conan instructions in getting-started page (#4537)
Signed-off-by: Uilian Ries <uilianries@gmail.com>
2025-09-10 11:04:48 -07:00
crueter
da97ba2914
[cmake] fix MASTER_PROJECT heuristic; only enable install if master project (#4536)
SOURCE vs CURRENT_SOURCE is unreliable. `DEFINED PROJECT_NAME` is a much more portable way of determination.

Also, installation shouldn't default to on if it is a subproject.
2025-09-10 11:01:25 -07:00
Victor Zverovich
79a5e93027 Update changelog 2025-09-10 10:59:23 -07:00
Fatih BAKIR
18e160eb4c
Add missing include for crtdbg.h (#4534)
This header uses _CRT_ASSERT, which is defined in crtdbg.h but does not
include it, causing a build error in format-test.cc

Fixes the issue by including the header
2025-09-08 09:37:43 -07:00
Victor Zverovich
53d006abfd Update changelog 2025-09-07 09:20:53 -07:00
Victor Zverovich
63bef33999 Update changelog 2025-09-07 08:36:07 -07:00
Victor Zverovich
d7cbfef11c Update changelog 2025-09-07 08:29:03 -07:00
Victor Zverovich
26b033e239 Update changelog 2025-09-07 07:54:59 -07:00
Victor Zverovich
609188cb92 Cleanup macro definitions 2025-09-07 07:44:49 -07:00
Victor Zverovich
84b9c00711 Update changelog 2025-09-07 07:44:11 -07:00
Marcel
9908d00037
Delete Bazel support (#4530) 2025-09-05 15:53:13 -07:00
Anas
0a94e06750
Combined emphases into a single escape sequence (#4528) 2025-09-05 09:50:56 -07:00
Victor Zverovich
053d262944 Add .clang-tidy 2025-09-03 11:05:49 -07:00
Victor Zverovich
e72e43af1c Apply clang-tidy 2025-09-03 11:05:41 -07:00
Victor Chernyakin
80549a630e
Avoid repeated call to GetLastError in file::size (#4522) 2025-09-03 08:38:31 -07:00
dependabot[bot]
f17b9aa44c
Bump actions/checkout from 4.2.0 to 5.0.0 (#4523)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 5.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](d632683dd7...08c6903cd8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 09:04:42 -07:00
Victor Zverovich
882702c219 Apply clang-tidy 2025-09-01 12:21:17 -07:00
Victor Zverovich
16d371b649 Apply clang-tidy 2025-09-01 12:00:35 -07:00
Victor Zverovich
619b3a5aa0 Suppress a false positive in clang-tidy 2025-09-01 11:42:00 -07:00
Victor Zverovich
79c7f8a70b Apply clang-tidy 2025-09-01 10:49:49 -07:00
Victor Zverovich
20e0d6d8dd Update docs 2025-09-01 10:15:54 -07:00
Victor Zverovich
8ba99c0f05 Update docs 2025-09-01 10:04:54 -07:00
Victor Zverovich
656228fbee Update docs 2025-09-01 09:50:45 -07:00
Victor Zverovich
02fb76d69b Add FMT_STATIC_FORMAT 2025-09-01 09:15:32 -07:00
Victor Zverovich
a57f196cad Apply coding conventions 2025-09-01 07:24:08 -07:00
Victor Zverovich
a75e8af487 Update docs 2025-08-31 10:51:29 -07:00
Victor Zverovich
e129591f02 Update changelog 2025-08-31 10:24:40 -07:00
Victor Zverovich
13d4f8469e Remove deprecated vformat_to 2025-08-31 10:02:36 -07:00
Victor Zverovich
e2f89e6d21 Remove deprecated aliases 2025-08-31 09:40:11 -07:00
Victor Zverovich
489fd7ca4b Simplify locale handling 2025-08-31 09:33:43 -07:00
Victor Zverovich
69324379f2 Mark detail::assert_fail as deprecated 2025-08-31 08:11:57 -07:00
Björn Schäpers
ff1caa58c9
Use FMT_THROW in report_error (#4521)
With exceptions: No change (if FMT_THROW is not user provided)

Without exceptions: Now also states where it comes from (report_error)
and uses fprintf instead of fputs.

My motivation is, now that I have my own fmt::assert_failed, also to get
rid of fputs for my embedded build.
2025-08-31 08:05:54 -07:00
Björn Schäpers
e181e94140
Add FMT_CUSTOM_ASSERT_FAIL (#4505)
That way one can provide ones own implementation for assert_fail, which
is moved out of the detail namespace. For binary compatibility the
detail version stays to call the outer version.
2025-08-25 09:02:00 -07:00
Victor Zverovich
43c88d00ae Update changelog 2025-08-24 10:08:53 -07:00
Victor Zverovich
33a7d55c9b Update changelog 2025-08-24 10:04:00 -07:00
Victor Zverovich
8e4676e4a5 Remove deprecated template parameter 2025-08-24 09:52:17 -07:00
Victor Zverovich
b5c4d25cd1 Rename is_char to is_code_unit 2025-08-24 09:30:50 -07:00
Victor Zverovich
a77efa4b4a Move is_*char to detail 2025-08-24 09:16:18 -07:00
Victor Zverovich
e7e71009c7 Make FP formatting compatible with exotic chars 2025-08-24 08:49:54 -07:00
Victor Zverovich
b7b261977e Simplify report_error 2025-08-24 08:30:17 -07:00
Victor Zverovich
61e0503daf Merge workarounds for bogus MSVC warnings 2025-08-24 08:18:47 -07:00
Victor Zverovich
72c82296d6 Put FMT_END_* together 2025-08-17 12:25:40 -07:00
Victor Zverovich
a402614e8c Remove deprecated aliases 2025-08-17 11:52:00 -07:00
Victor Zverovich
e719c43cc6 Apply coding conventions 2025-08-17 10:59:16 -07:00
Victor Zverovich
8a8ff6177c Remove deprecated and undocumented has_formatter 2025-08-17 10:52:45 -07:00
Victor Zverovich
5b99b334f0 Remove redundant else 2025-08-17 10:03:01 -07:00
Mattias Ljungström
9588458917
Silence unreachable code warnings in MSVC when FMT_USE_EXCEPTIONS are disabled. (#4515) 2025-08-17 09:35:28 -07:00
Victor Zverovich
f91dc80f4c Minor cleanup 2025-08-16 14:49:26 -07:00
Victor Zverovich
4120581167 Minor cleanup 2025-08-16 14:20:06 -07:00
Victor Zverovich
7ffc3ca15b Minor cleanup 2025-08-16 12:20:02 -07:00
Victor Zverovich
e9ddc97b9a Remove unnecesary constexpr and inline more 2025-08-10 08:40:35 -07:00
Murat Toprak
0d145936ec
Handle allocator propagation in basic_memory_buffer::move, Fix #4487 (#4490)
* Handle allocator propagation in basic_memory_buffer::move

Update `basic_memory_buffer::move` to respect `propagate_on_container_move_assignment`allocator trait.
If the allocator should not propagate and differs from the target's allocator,
fallback to copying the buffer instead of transferring ownership.

This avoids potential allocator mismatch issues and ensures exception safety.

* Add test cases for the updated move ctor

- Added two test cases `move_ctor_inline_buffer_non_propagating` and `move_ctor_dynamic_buffer_non_propagating`
- Added `PropageteOnMove` template parameter to `allocator_ref` class to be compatible with the old test cases
- `allocator_ref` now implements `!=` and `==` operators
2025-08-09 08:14:15 -07:00
Victor Chernyakin
8f3a965186
Refactor ansi_color_escape to track size instead of using a null terminator (#4511) 2025-08-09 08:08:41 -07:00
Victor Chernyakin
ae8cb1e8e6
Ignore some more files in .gitignore (#4512) 2025-08-05 13:41:50 -07:00
Victor Zverovich
add164f6b3 Optimize the default FP formatting 2025-08-03 12:46:38 -07:00
Victor Zverovich
23059d558e Fix exponent size computation 2025-08-03 10:00:24 -07:00
Victor Zverovich
505cc3d0c2 Simplify remove_trailing_zeros 2025-08-03 08:45:48 -07:00
Victor Zverovich
b8a502615d Revert "Optimize the default FP formatting"
This reverts commit 93f03953af6b0268e1a29bb5b23d50f72b87a151.
2025-08-03 07:40:13 -07:00
dependabot[bot]
814f51eab6
Bump github/codeql-action from 3.28.16 to 3.29.5 (#4510)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.29.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](28deaeda66...51f77329af)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 11:52:42 -07:00
Victor Zverovich
93f03953af Optimize the default FP formatting 2025-07-27 08:56:07 -07:00
Dominic Pöschko
35dcc58263
fix buffer overflow on all emphasis flags set (#4498) 2025-07-20 10:43:33 -07:00
Catherine
553ec11ec0
Make compatible with WASI (#4497)
WASI is a POSIX subset that doesn't have `dup`, `dup2`, or `pipe` system calls.

Fixes #4496.
2025-07-14 15:51:04 -07:00
autoantwort
a0ecfe3e1c
msvc + ninja + modules: Fix build when consuming fmtlib while using a Ninja generator (#4495)
Co-authored-by: Leander Schulten <Leander.Schulten@tetys.de>
2025-07-13 10:28:00 -07:00
Victor Zverovich
127413ddaa
Add OpenSSF Best Practices badge 2025-07-12 07:12:18 -07:00
Victor Zverovich
7d29ebe4af Minor cleanup 2025-07-12 07:09:38 -07:00
anonymous
20c8fdad06
Fix import std in clang++ (#4488)
fix module-compilation error when defined `FMT_IMPORT_STD`
2025-07-07 11:47:48 -07:00
Victor Zverovich
300ce75ca6 Handle invalid glibc FILE buffer 2025-07-06 11:17:16 -07:00
Victor Zverovich
513f978241 Cleanup os-test 2025-07-04 10:52:59 -07:00
Victor Zverovich
6a3b40524c Use actual example code and move safe_fopen to os-test 2025-07-04 09:50:55 -07:00
Victor Chernyakin
2fa3e1a1bb
Fix interaction between debug presentation, precision, and width for strings (#4478) 2025-07-04 07:33:47 -07:00
dependabot[bot]
fc8d07cfe5
Bump msys2/setup-msys2 from 2.27.0 to 2.28.0 (#4485)
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.27.0 to 2.28.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](61f9e5e925...40677d36a5)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-version: 2.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 08:42:01 -07:00
Miuna
27c5aab349
Fix unwanted char promotion in decimal writer with wchar_t (#4483) 2025-07-01 07:03:57 -07:00
Victor Zverovich
bc0193535a Update image 2025-06-29 07:50:09 -07:00
Miuna
353bd895a2
Add FMT_EXPORT on ranges.h customization points (#4476) 2025-06-24 10:30:44 -07:00
Tomek-Stolarczyk
953cffa701
Replace memset with constexpr fill_n in bigint::align (#4471)
Use fill_n in place of memset in bigint::align to respect constexpr.
2025-06-23 16:18:16 -07:00
Vladislav Shchapov
571c02d475
Add xchar support for std::byte formatter (#4480)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-06-23 14:02:11 -07:00
Remy Jette
f4345467fc
Fix compilation on clang-21 / libc++-21 (#4477)
`<cstdlib>` was not being included, so malloc and free were only declared
via transitive includes. Some includes changed in the latest libc++-21
build which broke fmt.

Also changed `malloc`/`free` to `std::malloc` and `std::free`, as
putting those symbols in the global namespace is optional for the
implementation when including `<cstdlib>`.
2025-06-21 07:28:14 -07:00
Victor Chernyakin
1ef8348070
Properly constrain detail::copy optimization (#4474) 2025-06-21 06:59:35 -07:00
Sahil Sinha
a5dccffa56 Add double and float support to scan test
- Add double_type and float_type to scan_type enum
- Add double* and float* pointers to scan_arg union
- Add constructors for double and float scan arguments
- Add switch cases for double and float types in visit()
- Implement basic read() functions for floating-point parsing

This partially resolves the TODO comment 'more types' in scan.h by adding
support for the two most commonly needed floating-point types.
2025-06-21 06:57:20 -07:00
Victor Zverovich
4a149f513f Test non-SSO constexpr string formatting 2025-06-20 07:10:12 -07:00
Victor Chernyakin
067bc479b4
Avoid redundant work when processing UTF-8 strings (#4475) 2025-06-20 06:39:06 -07:00
Victor Zverovich
730fd4d9a7 Remove redundant tests 2025-06-08 08:46:22 -07:00
Mikhail Svetkin
5860688d7e
Enable constexpr support for fmt::format (fmtlib#3403) (#4456) 2025-06-07 07:16:49 -07:00
Victor Zverovich
46be88bc1e Cleanup FP formatting 2025-06-01 09:15:20 -07:00
Thomas Khyn
cc88914904
Export fmt::dynamic_format_arg_store in fmt module (#4459) 2025-06-01 08:50:14 -07:00
Victor Zverovich
fc0c76a075 Handle large precision 2025-06-01 08:49:41 -07:00
dependabot[bot]
6332a38529
Bump ossf/scorecard-action from 2.4.0 to 2.4.2 (#4462)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.2.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](62b2cac7ed...05b42c6244)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-01 08:48:36 -07:00
Victor Zverovich
02de29e003 Remove a reference to a compromised account 2025-05-30 18:24:56 -07:00
Victor Zverovich
6d51c78c1e Cleanup FP formatting 2025-05-30 16:45:21 -07:00
Victor Zverovich
0f4e9d0bde Cleanup FP formatting 2025-05-30 16:05:57 -07:00
Victor Zverovich
d9d50495ac Optimize the default FP formatting 2025-05-30 13:45:04 -07:00
Victor Zverovich
befbc5fdb8 Fix ADL lookup for memory_buffer 2025-05-26 09:44:35 -07:00
Victor Zverovich
8aa1d6a9fb Minor cleanup 2025-05-25 10:14:24 -07:00
Nikhil
6d79757a38
Interpret precision as display width (#4443) 2025-05-25 08:42:47 -07:00
Victor Zverovich
1ff0b7f5e1 Cleanup warning suppression 2025-05-24 09:37:01 -07:00
Edoardo Morandi
ea985e84f8
Remove some implicit conversions (#4447)
* fix: avoid an implicit cast

The "1" used for the bitshift is treated as int, and this causes an
implicit conversion to `UInt` when performing the logical and.
Explicitly casting the number to `UInt` avoids the warning.

* fix: avoid implicit conversions for indices

Some indices in `include/fmt/base.h` are expressed as `int` types, which
causes an implicit conversion to a `size_t` when they are actually used
as index. Explicitly casting the value avoids the warning.

* fix: avoid an implicit conversion using size_t

The number of bits is used to express the size of a buffer. Using an
`int` causes an implicit conversion warning, let's use a `size_t` which
is the right type for the job.
2025-05-24 09:22:03 -07:00
Andreas Reischuck
f7033da09e
Avoid include locale inline if C++20 modules are enabled (#4451)
MSVC hints with:
```
fmt\include\fmt\format-inl.h(26): warning C5244: '#include <locale>' in the purview of module 'fmt' appears erroneous.  Consider moving that directive before the module declaration, or replace the textual inclusion with 'import <locale>;'.
```

Then fails the build with `type redefinition`.
2025-05-21 17:18:18 -07:00
Tobias Schlüter
b723c021df
Give useful error when misusing fmt::ptr. (#4453)
static_assert(bla, "") prints an empty message but not the condition with at least MSVC. Add an informative message.
2025-05-20 12:21:06 -07:00
Victor Zverovich
3ba3c390fb Clarify that formatting of pointers is disallowed 2025-05-17 10:16:58 -07:00
Kefu Chai
ab161a71c6
Fix some typos in comments (#4448)
- s/instantion/instantiation/
- s/uninitalized/uninitialized/
- s/costexpr/constexpr/

Signed-off-by: Kefu Chai <tchaikov@gmail.com>
2025-05-15 06:28:14 -07:00
Victor Chernyakin
b5266fd3b9
Remove some redundant consts (#4445)
`constexpr` variables are implicitly `const`.
2025-05-12 10:41:58 -07:00
Victor Zverovich
9b0ebd4435 Cleanup base-test 2025-05-11 15:42:08 -07:00
Victor Zverovich
7af94e5597 Remove old gcc workaround 2025-05-11 12:35:28 -07:00
Victor Zverovich
2924fcf8f6 Cleanup base-test 2025-05-11 10:36:42 -07:00
Victor Zverovich
102752ad45 Update docs 2025-05-11 09:13:12 -07:00
Victor Zverovich
a6cd72c9eb Cleanup base-test 2025-05-11 09:01:28 -07:00
Victor Zverovich
07885271a1 Minor cleanup 2025-05-11 07:54:23 -07:00
Jeremy Rifkin
4999416e5c
Fix reference_wrapper ambiguity with format_as (#4434) 2025-05-10 11:15:45 -07:00
n-stein
55a8f6a4be
Change component prefix for NSIS compatibility (#4442) 2025-05-09 12:09:18 -07:00
Victor Zverovich
eb9a95d426 Clarify that formatting of pointers is disallowed 2025-05-05 10:55:59 -07:00
Victor Zverovich
d5c33e4f45 Make template parameter order consistent 2025-05-04 15:23:48 -07:00
Victor Zverovich
a2225f2887 Remove unused include 2025-05-04 15:16:38 -07:00
Victor Zverovich
b43b2f9537 Cleanup standard formatters 2025-05-04 13:04:06 -07:00
Victor Zverovich
1312b4a162 Cleanup standard formatters 2025-05-04 12:37:28 -07:00
Victor Zverovich
4404dc05dd Consolidate implementation details 2025-05-04 10:48:47 -07:00
Victor Zverovich
7bb6fcb325 Bump version 2025-05-04 09:36:07 -07:00
Victor Zverovich
59259a5fde Make a doc directory if it doesn't exist 2025-05-03 10:29:35 -07:00
Victor Zverovich
542ea7c40b Clarify that Formatter parameter is deprecated 2025-05-03 10:28:46 -07:00
Victor Zverovich
40626af88b Update version 2025-05-03 09:36:27 -07:00
Victor Zverovich
7fdd6846ba Bump version 2025-05-03 09:02:35 -07:00
Victor Zverovich
6caff7ed9c Cleanup test 2025-05-03 08:36:42 -07:00
Victor Zverovich
71a5483875 Update changelog 2025-05-03 07:29:31 -07:00
Victor Zverovich
448929d491 Update and apply clang-format 2025-05-03 07:29:31 -07:00
dependabot[bot]
26d87edab1
Bump github/codeql-action from 3.28.13 to 3.28.16 (#4432)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.13 to 3.28.16.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b549b9259...28deaeda66)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-03 06:59:36 -07:00
Victor Zverovich
505ee058f7 Update changelog 2025-04-27 11:57:40 -07:00
Victor Zverovich
ccab417195 Update changelog 2025-04-27 11:57:15 -07:00
Victor Zverovich
ec1349d348 Update changelog 2025-04-27 11:56:07 -07:00
Victor Zverovich
0ed2a65a8a Clarify why we use __builtin_strlen instead of strlen 2025-04-27 11:08:27 -07:00
Victor Zverovich
e22c943070 Update changelog 2025-04-27 10:59:31 -07:00
Victor Zverovich
b252bad3c6 Update changelog 2025-04-27 10:29:56 -07:00
Victor Zverovich
2680831231 Cleanun string_view 2025-04-27 09:34:38 -07:00
Barry Revzin
8978ab09b2
Avoiding __builtin_strlen (#4429) 2025-04-27 09:32:10 -07:00
Victor Zverovich
c936e2e44e Implement debug format for error_code 2025-04-27 09:06:17 -07:00
Victor Zverovich
a7d7b894cd Implement the s specifier for error_code 2025-04-27 08:51:18 -07:00
Victor Zverovich
e98155a6fb Remove redundant specializations 2025-04-26 10:41:03 -07:00
Victor Zverovich
41b3bed4d2 Clarify why we don't use qualified names 2025-04-26 10:12:06 -07:00
Victor Zverovich
67d9e49322 Update changelog 2025-04-26 09:15:47 -07:00
Victor Zverovich
9db5e4df22 Don't specialize std::is_floating_point 2025-04-26 08:17:05 -07:00
Victor Zverovich
906eaf2ddb Make specifier order consistent 2025-04-25 12:10:21 -07:00
Victor Zverovich
9f6c12c3dc Remove deprecated localtime from docs 2025-04-25 11:58:58 -07:00
rlalik
2d0518b5f7
Fix cmake error in pedantic mode (#4426)
Suppresses error:
CMake Error (dev) at build/coverage/_deps/fmt-src/CMakeLists.txt:208 (set):
  uninitialized variable 'CMAKE_MODULE_PATH'
This error is for project developers. Use -Wno-error=dev to suppress it.
2025-04-24 11:41:33 -07:00
Victor Zverovich
c81cbed2b7 Simplify test 2025-04-23 12:32:26 -07:00
krzysztofkortas
c7925241c7
Remove core.h from README (#4422) 2025-04-21 11:27:40 -07:00
LocalSpook
c709138359 Add support for incomplete types 2025-04-20 10:16:11 -07:00
krzysztofkortas
db405954cd
Remove fmt/core.h from docs (#4421) 2025-04-20 07:40:18 -07:00
Victor Zverovich
0a917ee2f5 Minor comment tweak 2025-04-19 10:18:01 -07:00
Victor Zverovich
969d4aef60 Update doc image 2025-04-19 10:08:24 -07:00
Victor Zverovich
8061c7c8c4 Cleanup duration formatter 2025-04-19 10:08:24 -07:00
Victor Zverovich
7b59df4119 Remove redundant member 2025-04-19 10:08:23 -07:00
hirohira
b8192d233a
Fix build error with MSVC v141 (#4413) 2025-04-15 08:21:38 -07:00
Victor Zverovich
e814b5fabf Reduce template parametrization 2025-04-13 10:17:17 -07:00
Victor Zverovich
ed0d216f7e Fix localization and formatting of timezone names 2025-04-13 09:52:59 -07:00
Victor Zverovich
bd9554a29e Fix formatting of timezone names 2025-04-13 08:52:26 -07:00
Victor Zverovich
f086dc0d27 Fix timezone handling in tm 2025-04-13 08:23:40 -07:00
Victor Zverovich
f10b6dd816 Improve chrono formatting 2025-04-12 09:59:06 -07:00
Victor Zverovich
f470b9c566 Cleanup chrono tests and set consistent TZ 2025-04-12 09:12:49 -07:00
Victor Zverovich
b28214487d Fix handling of %Z 2025-04-12 08:51:22 -07:00
Victor Zverovich
6d69f0c5f2 Improve chorno tests 2025-04-12 08:24:49 -07:00
Victor Zverovich
da776c9a66 Test timezone 2025-04-12 07:53:58 -07:00
Mattes D
64db979e38
Added a missing FMT_STRING in fmt::println() (#4407) 2025-04-07 15:35:55 -07:00
Victor Zverovich
5f2e61fdd5 Cleanup chrono detail 2025-04-06 09:54:47 -07:00
Victor Zverovich
b3d45e1d3f Remove fmt_detail 2025-04-06 09:24:20 -07:00
dependabot[bot]
5f6fb96df1
Bump github/codeql-action from 3.28.8 to 3.28.13 (#4403)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.13.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](dd746615b3...1b549b9259)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 13:29:20 -07:00
Victor Zverovich
5199e0f885 Fix a flush issue on libstdc++ 2025-03-30 10:54:14 -07:00
Victor Zverovich
2f58430573 Move buffering tests to os-test 2025-03-30 09:38:40 -07:00
Vertexwahn
d5d32c1e81
Bazel support: Update platforms to 0.0.11 (#4400) 2025-03-30 08:36:25 -07:00
Victor Zverovich
204661287b Improve local_time test 2025-03-29 10:56:11 -07:00
Victor Zverovich
e1ab383361 Report an error when timezone is not available 2025-03-29 09:10:00 -07:00
Victor Zverovich
b9e0e94a01 Enable more chrono tests on Windows 2025-03-29 08:20:49 -07:00
Victor Zverovich
a81842428d Update changelog 2025-03-29 08:14:25 -07:00
Victor Zverovich
f53055efe0 Revert "Workaround an ABI issue in spdlog"
This reverts commit 784eac839df77df1852dc1c50b17b76c3aec8f4a.
2025-03-29 07:48:20 -07:00
Victor Zverovich
b2dfcb2b80 Fix local_time test 2025-03-23 19:51:33 -07:00
Victor Zverovich
7ac97cbd1d Enable some local_time tests and make them deterministic 2025-03-23 11:50:03 -07:00
Victor Zverovich
17898794a9 Use fmt::local_time 2025-03-23 11:18:14 -07:00
Victor Zverovich
443a8ef342 Deprecate fmt::localtime 2025-03-23 10:46:02 -07:00
Victor Zverovich
3607e92dc9 Bump version 2025-03-23 10:44:46 -07:00
Victor Zverovich
43e31614cc Test ambiguous time 2025-03-23 10:34:37 -07:00
Victor Zverovich
989826ce50 Update changelog 2025-03-22 08:03:54 -07:00
Victor Zverovich
9d6e24c64e Fix handling of long with FMT_BUILTIN_TYPES=0 2025-03-22 07:52:54 -07:00
Victor Zverovich
0843317e08 Update changelog 2025-03-22 07:25:46 -07:00
Victor Zverovich
784eac839d Workaround an ABI issue in spdlog 2025-03-22 07:01:45 -07:00
Victor Zverovich
6fdf225a32 Always inline value ctors in optimized gcc mode only
This reverts commit 332da79bf37d06bb549810fd9420fd47407ebd89.
2025-03-16 10:03:53 -07:00
Victor Zverovich
332da79bf3 Always inline value ctors 2025-03-16 09:31:52 -07:00
Victor Zverovich
7b273fbb54 Minor cleanup 2025-03-16 08:58:44 -07:00
Victor Zverovich
191c504b10 Cleanup build config 2025-03-16 08:24:31 -07:00
Victor Zverovich
d13fb6092f Cleanup build config 2025-03-15 12:45:36 -07:00
Victor Zverovich
dd780fde44 Add clang-3.4 2025-03-15 12:22:10 -07:00
Dean Glazeski
37e6474718
Fix dynamic named arg format spec handling (#4361)
When dealing with dynamic named format args, need to account for
nested named args when skipping the content of the replacement.

Fixes #4360
2025-03-15 09:34:11 -07:00
Victor Zverovich
77c0fc07d9 Switch to supported ubuntu image 2025-03-09 17:43:29 -07:00
Victor Zverovich
9212ff6ca1 Apply coding conventions and use constexpr 2025-03-02 09:03:06 -08:00
Dean Glazeski
864bdf9638
Report error on duplicate named arg names (#4367) 2025-03-02 07:47:03 -08:00
Victor Chernyakin
b776cf66fc
Optimize text_style using bit packing (#4363) 2025-03-01 11:18:19 -08:00
dependabot[bot]
bdbf957b9a Bump msys2/setup-msys2 from 2.25.0 to 2.27.0
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.25.0 to 2.27.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](c52d1fa9c7...61f9e5e925)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-01 07:56:45 -08:00
Thomas Khyn
577fd3be88 Fix TU-local entity exposition error in GCC 15 2025-02-26 11:14:15 -08:00
Thomas Khyn
faac8b1fa9 Remove exports in std.h
This fixes 'declaration of partial specialization in unbraced export-declaration' errors in GCC 15
2025-02-26 11:14:15 -08:00
Victor Zverovich
123913715a Update version 2025-02-26 10:14:28 -08:00
Victor Zverovich
8c1059b92e Update changelog 2025-02-25 17:32:17 -08:00
Victor Zverovich
4e5aafbf43 Bump version 2025-02-25 17:20:47 -08:00
Victor Zverovich
db30fb3b81 Update changelog 2025-02-25 17:20:29 -08:00
Victor Zverovich
3401ce2be2 Fix ABI compatibility 2025-02-25 13:32:24 -08:00
Dean
7f7695524a
Fix conflict with std::ignore (#4356)
In situations where `using namespace std;` is used, compiler warnings
can be generated because of local variables named `ignore`. This renames
those variables to something else to address the name conflict.
2025-02-19 23:08:21 -08:00
Dean
251320fcb7
Add .vs folder to .gitignore (#4355)
Opening the fmt folder as a CMake project with Visual Studio creates
this directory. The contents can be ignored.
2025-02-19 22:44:31 -08:00
Victor Chernyakin
94ab51cb8c
Simplify implementation of operator""_cf (#4349) 2025-02-14 02:02:09 -08:00
Victor Zverovich
0ca42e836e Workaround an MSVC v140 bug 2025-02-13 14:12:16 +01:00
Thomas Khyn
ed27df5760
Replace forward slashes by backslashes in BMI path for MSVC. (#4344)
* Fix slashes in BMI path for MSVC builds
* Fix BMI path for MSVC builds when building with Ninja generator
2025-02-10 00:18:33 -08:00
Victor Zverovich
d42a068dbd Apply coding conventions 2025-02-05 11:06:45 -08:00
Markus Kitsinger
f2cec917da
Move is_compiled_string to public API (#4342) 2025-02-04 16:20:27 -08:00
Sergiu Deitsch
d5b866e242
fix gcc 8.3 compile errors (#4336) 2025-02-03 08:51:58 -08:00
dependabot[bot]
5676e408f5
Bump github/codeql-action from 3.27.0 to 3.28.8 (#4337)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.0 to 3.28.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](662472033e...dd746615b3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-01 12:08:34 -08:00
dependabot[bot]
71d24b564d
Bump actions/upload-artifact from 4.4.0 to 4.6.0 (#4339)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.0 to 4.6.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](50769540e7...65c4c4a1dd)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-01 11:30:13 -08:00
LocalSpook
c9267da4df Fix typo in FMT_HAS_BUILTIN check 2025-01-31 08:42:19 -08:00
Victor Zverovich
373855c1b0 Clarify difference in FP representation 2025-01-26 13:49:54 -08:00
Victor Zverovich
52eeeb52a6 Make exponent threshold depend on representation (#3649) 2025-01-26 12:10:48 -08:00
Victor Zverovich
9cf9f38ede Update version 2025-01-25 10:08:57 -08:00
Victor Zverovich
4946bdb729 Update changelog 2025-01-25 08:53:02 -08:00
Rafał Lalik
01a5b56f0d Fix error of unitialized variable FMT_HEADERS
This happens when using e.g. pedantic mode in cmake-init.
2025-01-25 08:41:49 -08:00
timsong-cpp
cb6fdf2191
Restore constraint on map formatter (#4326)
* Restore constraint on map formatter
* Remove unnecessary double parens
2025-01-25 08:31:07 -08:00
timsong-cpp
f841ae61e2
Fix #4303: avoid instantiating formatter<const T> (#4325) 2025-01-24 10:53:10 -08:00
Matt
a3d05d70ce
Silence a constexpr warning when compiling with MSVC and /W4 (#4322) 2025-01-23 12:11:23 -08:00
Victor Zverovich
41539c29f3 Workaround a bug in gcc 6 (#4318) 2025-01-22 11:12:41 -08:00
Victor Zverovich
aabe63910c Tweak changelog 2025-01-20 09:33:33 -08:00
Victor Zverovich
f90090be2c Update changelog 2025-01-19 10:00:40 -08:00
Victor Zverovich
9ff9c695db Bump version 2025-01-18 10:28:46 -08:00
Victor Zverovich
06ad1224eb Update changelog 2025-01-18 10:05:40 -08:00
Victor Zverovich
5f0572acdc Workaround a compilation error on gcc 9.4 2025-01-18 09:01:00 -08:00
Vladislav Shchapov
898d438571
Fix formatting into std::ostreambuf_iterator using a compiled format (#4312)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-18 07:16:06 -08:00
Aaron Bishop
937b7c5c10
Add args() accessor back to fmt::format_context (#4310)
Add args() accessor back to fmt::format_context
Add test that would fail to compile if you can't create a fmt::format_context from another fmt::format_context
2025-01-17 10:28:34 -08:00
Victor Zverovich
01914f0389 Reduce size of basic_specs 2025-01-12 09:18:11 -08:00
Victor Zverovich
c43da35701 Workaround an ICE when using modules with gcc 14.2 and earlier 2025-01-12 08:57:43 -08:00
Victor Zverovich
8303d140a1 Update version 2025-01-12 08:15:44 -08:00
Victor Zverovich
b0b3dc5ff9 Bump version 2025-01-12 08:13:56 -08:00
Victor Zverovich
586ea06f02 Rename set_fill to copy_fill_from 2025-01-11 09:22:15 -08:00
Victor Zverovich
5750f434fa Update changelog 2025-01-11 09:06:00 -08:00
Marcel Breyer
bfbdc2be9a Add parameter to the fallback to_sys function. 2025-01-11 08:48:20 -08:00
Victor Zverovich
87e0072673 Update changelog 2025-01-11 07:21:28 -08:00
Victor Zverovich
d57040f949 Prefix components 2025-01-10 19:51:55 -08:00
Victor Zverovich
21aa0956d4 Restore ABI compatibility 2025-01-10 17:36:09 -08:00
Edoardo Lolletti
3f864a4505
Address MSVC C4127 warning when formatting non unicode tm (#4299)
Use `const_check` to silence visual studio's W4 level diagnostic regarding conditional expressions being constants, addresses https://github.com/fmtlib/fmt/issues/4294
2025-01-08 14:21:48 -08:00
Trim21
093b39ca5e
Update docs for meson (#4291) 2025-01-06 10:52:57 -08:00
Vladislav Shchapov
2c3a5698ef Simplify a copying the fill from basic_specs
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-05 09:33:05 -08:00
Victor Zverovich
fc1b0f3486 Clarify use of FMT_THROW in a comment 2025-01-05 07:31:19 -10:00
GamesTrap
1d066890c7 Resolve C4702 unreachable code warnings 2025-01-03 16:39:11 -08:00
Vladislav Shchapov
dad3237514 Fix a bug when copying the fill from basic_specs
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-03 09:16:21 -08:00
Vladislav Shchapov
880e1494dc Improve xchar support for std::bitset formatter
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-03 09:16:21 -08:00
Victor Zverovich
e3ddede6c4 Update version 2024-12-27 08:41:16 -08:00
Victor Zverovich
e9ec4fdc88 Bump version 2024-12-27 08:41:06 -08:00
Victor Zverovich
feb72126b4 Readd FMT_NO_UNIQUE_ADDRESS 2024-12-26 13:54:06 -08:00
Victor Zverovich
8d517e54c9 Update changelog 2024-12-26 13:50:13 -08:00
Victor Zverovich
563fc74ae4 Update changelog 2024-12-26 10:59:56 -08:00
Victor Zverovich
3e04222d53 Restore ABI compatibility with 11.0.2 2024-12-26 10:45:15 -08:00
Victor Zverovich
853df39d0a Mention compile-time formatting 2024-12-26 09:48:10 -08:00
Victor Zverovich
11742a09c7 Clarify that format_string should be used instead of fstring 2024-12-26 09:39:01 -08:00
Victor Zverovich
da24fac101 Document fstring 2024-12-26 09:19:31 -08:00
Carl Smedstad
5fa4bdd758
Define CMake components to allow docs to be installed separately (#4276)
Define two components, core and doc, which can be installed separately.
This facilitates packagers who want to package docs in a separate
package.

After this change it's possible to install only core files with:

     cmake --install build --component core

And only install documentation with:

     cmake --install build --component doc

When no component is specified, the behaviour is unchanged, i.e. if
documentation was built, it will be installed.
2024-12-26 07:13:01 -08:00
Victor Zverovich
3c8aad8df7 Update the release script 2024-12-26 07:03:15 -08:00
Victor Zverovich
0e8aad961d Update version 2024-12-25 08:46:43 -08:00
Victor Zverovich
debe784aab Update changelog 2024-12-25 08:44:54 -08:00
Victor Zverovich
f6d1125676 Update changelog 2024-12-25 08:40:19 -08:00
Victor Zverovich
73d0d3f75d Fix github API call 2024-12-25 08:27:59 -08:00
Victor Zverovich
08f60f1efc Update changelog 2024-12-25 08:27:33 -08:00
Victor Zverovich
faf3f84085 Bump version 2024-12-25 08:18:51 -08:00
Victor Zverovich
f3a41441df Replace requests with urllib 2024-12-25 08:16:09 -08:00
Victor Zverovich
3f33cb21d2 Update changelog 2024-12-25 07:54:45 -08:00
Victor Zverovich
b07a90386e Update changelog 2024-12-25 07:52:52 -08:00
Victor Zverovich
a6fba51773 Update changelog 2024-12-25 07:49:56 -08:00
Victor Zverovich
25e2929988 Update changelog 2024-12-25 07:44:58 -08:00
Victor Zverovich
00ab2e98b5 Update changelog 2024-12-25 07:38:42 -08:00
Victor Zverovich
a3ef285aec Always inline const_check to improve debug codegen in clang 2024-12-24 12:54:44 -08:00
Victor Zverovich
28d1abc9d2 Update changelog 2024-12-24 09:12:32 -08:00
Victor Zverovich
90704b9efd Update changelog 2024-12-23 14:55:33 -08:00
YexuanXiao
86dae01c23
Fix compatibility with older versions of VS (#4271) 2024-12-23 07:26:11 -08:00
Victor Zverovich
d8a79eafdc Document formatting of bit-fields and fields of packed structs 2024-12-21 11:06:11 -08:00
YexuanXiao
7c3d0152e5
Use the _MSVC_STL_UPDATE macro to detect STL (#4267) 2024-12-19 06:47:13 -08:00
Hannes Harnisch
7c50da5385
Allow getting size of dynamic format arg store (#4270) 2024-12-18 19:54:35 -08:00
Sascha Hestermann
873670ba3f Make parameter basic_memory_buffer<char, SIZE>& buf of to_string const 2024-12-11 12:19:20 -08:00
Victor Zverovich
735d4cc05e Update changelog 2024-12-11 12:17:40 -08:00
Vladislav Shchapov
141380172f
Allow disabling <filesystem> by define FMT_CPP_LIB_FILESYSTEM=0 (#4259)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-12-09 07:13:24 -08:00
Victor Zverovich
4302d74293 Update changelog 2024-12-08 07:20:31 -08:00
Victor Zverovich
0f51ea79d3 Update changelog 2024-12-07 11:05:40 -08:00
Alex Hirsch
9600fee020
Include <filesystem> only if FMT_CPP_LIB_FILESYSTEM is set (#4258)
This change results out of necessity since the Nintendo Switch console
SDK does not support `std::filesystem`. The SDK still provides the
`<filesystem>` header, but with an `#error` directive, effectively
breaking any build that includes `<filesystem>`

Because `<filesystem>` is present, `FMT_HAS_INCLUDE` is insufficient
here. With this change and `FMT_CPP_LIB_FILESYSTEM` in place, one can
define `FMT_CPP_LIB_FILESYSTEM=0` to work around this issue.

This assumes that `<filesystem>` can be included (without warnings) if
`FMT_CPP_LIB_FILESYSTEM` is set. If this is not the case, fmt would be
broken even before this change as `std::filesystem::path` is used
without the accompanying header.
2024-12-07 06:45:54 -08:00
dependabot[bot]
47a66c5ecc
Bump msys2/setup-msys2 from 2.24.0 to 2.25.0 (#4250)
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.24.0 to 2.25.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](5df0ca6cbf...c52d1fa9c7)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 09:24:57 -08:00
jsirpoma
385c01dc7b
Allow bit_cast to work for 80bit long double (#4246) 2024-11-29 01:25:21 -08:00
Victor Zverovich
df249d8ad3 Remove an old workaround 2024-11-19 14:28:39 +01:00
Victor Zverovich
dfad80d1c5 Remove an old workaround 2024-11-19 14:22:44 +01:00
Justin Riddell
536cabd562
Export all range join overloads (#4239) 2024-11-15 13:01:59 -08:00
Victor Zverovich
b1a054706e Remove more MSVC 2015 workarounds and fix string_view checks 2024-11-15 08:33:30 -08:00
Victor Zverovich
bfd95392c7 Remove MSVC 2015 workaround 2024-11-15 08:19:01 -08:00
Justin Riddell
9ced61bca4
Replace std::forward for clang-tidy (#4236)
Should fix #4231
2024-11-14 09:06:30 -08:00
Victor Zverovich
75e5be6adc Sort specifiers 2024-11-13 13:01:13 -08:00
nikola-sh
a169d7fa46
Fix chrono formatting syntax doc (#4235) 2024-11-13 12:57:22 -08:00
Victor Zverovich
a6c45dfea8 Fix modular build 2024-11-10 09:06:50 -08:00
Victor Zverovich
a35389b3c2 Corrently handle buffer flush 2024-11-09 10:56:31 -08:00
Vladislav Shchapov
5a3576acc8
Implement fmt::join for tuple-like objects (#4230)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-11-09 08:28:46 -08:00
Vladislav Shchapov
542600013f
Suppress MSVC warnings "C4127: conditional expression is constant" by used const_check (#4233)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-11-09 07:43:46 -08:00
Victor Zverovich
720da57bab Remove reference to unused intrinsic 2024-11-03 17:18:33 -08:00
Victor Zverovich
680db66c3a Explicitly export symbols from detail 2024-11-03 09:13:17 -08:00
Victor Zverovich
56ce41ef63 Remove initializer_list dependency 2024-11-03 08:26:25 -08:00
Victor Zverovich
cf50e4d6a4 Fix const[expr] in context API 2024-11-03 07:25:52 -08:00
Victor Zverovich
6580d7b808 Cleanup the format API 2024-11-03 06:57:36 -08:00
Victor Zverovich
7e73566ce7 Minor cleanup 2024-11-02 11:24:24 -07:00
Victor Zverovich
8523dba2dc Make constexpr precede explicit consistently 2024-11-02 11:03:03 -07:00
Victor Zverovich
e3d3b24fc1 Minor cleanup 2024-11-02 10:46:58 -07:00
Victor Zverovich
1521bba701 Use consistent types for argument count 2024-11-02 08:08:36 -07:00
dependabot[bot]
00649552a8
Bump github/codeql-action from 3.26.6 to 3.27.0 (#4223)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.6 to 3.27.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](4dd16135b6...662472033e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-01 09:13:46 -07:00
Victor Zverovich
4b8e2838f0 More cleanup 2024-10-27 10:56:52 -07:00
Victor Zverovich
7d4662f7ab Remove FMT_BUILTIN_CTZ 2024-10-27 10:22:43 -07:00
Victor Zverovich
27110bc474 Minor cleanup 2024-10-27 09:44:25 -07:00
Sergey
68f3153762
Fix narrowing conversion warning in struct fstring (#4210)
Warning C4267: 'initializing': conversion from 'size_t' to 'const int', possible loss of data.
2024-10-27 06:41:20 -07:00
Vladislav Shchapov
168df9a064
Implement fmt::format_to into std::vector<char> (#4211)
* Implement fmt::format_to into std::vector<char>

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

* Move get_container to the trait

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

---------

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-10-26 09:23:59 -07:00
Sergey
4daa3d591f
Fix error: cannot use 'try' with exceptions disabled in Win LLVM Clang (#4208)
Fixes #4207.

LLVM Clang on Windows does not define `__GNUC__`. The preprocessor falls
to `#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS` with `_HAS_EXCEPTIONS 1`
defined in vcruntime.h:104.
2024-10-23 10:43:55 -07:00
Victor Zverovich
e9eaa27e5a Add std::exception to the docs 2024-10-20 09:42:29 -07:00
Victor Zverovich
2b6a786e35 Use standard context in print 2024-10-20 09:08:24 -07:00
Victor Zverovich
a16ff5787b Add support for code units > 0xFFFF in fill 2024-10-20 07:59:58 -07:00
Victor Zverovich
601be1cbe7 Add support for code units > 0xFFFF in fill 2024-10-19 08:15:50 -07:00
Vinay Yadav
58c185b634
Changing type of data_ to size_t to avoid compilation warnings (#4200)
Changing type data_ to size_t because
1. If lib is cross-compiled for win32 using MXE environment it cause
   compilation warning -Wconversion on line 730 as sizeof(unsigned long)
   = 4 and sizeof(size_t) = 8
2. When lib is compiled on Unix like compiler generate warning
   -Wuseless-cast if static_cast is used to fix issue in 1
2024-10-19 06:59:21 -07:00
Victor Zverovich
a0a9ba2afc Fix hashes 2024-10-18 12:33:42 -07:00
Victor Zverovich
cc2ba8f9ed Cleanup cifuzz action 2024-10-13 11:16:31 -07:00
Victor Zverovich
a18d42b208
Simplify lint (#4197) 2024-10-13 09:28:38 -07:00
Yi Kong
4046f97278
Fix -Wmissing-noreturn warning (#4194)
Fixes warning reported by top of trunk Clang:

include/fmt/chrono.h:447:36: error: function 'throw_duration_error' could be declared with attribute 'noreturn'
2024-10-10 08:59:56 -07:00
Victor Zverovich
6bdc12a199 detail_exported -> detail 2024-10-09 11:04:55 -07:00
Victor Zverovich
786a4b0968 Cleanup fixed_string 2024-10-09 08:42:49 -07:00
Victor Zverovich
2cb3b7c64b
Update README.md 2024-10-06 07:39:03 -07:00
Victor Zverovich
e9cba69057
Update README.md 2024-10-06 07:38:41 -07:00
Victor Zverovich
02537548f3 Cleanup an example 2024-10-04 17:15:07 -07:00
Victor Zverovich
c68c5fa7c7 Test FMT_BUILTIN_TYPES 2024-10-04 16:08:46 -07:00
Haowei
22701d5f63
Address build failures when using Tip-of-Tree clang. (#4187)
When using ToT clang to build fmtlib, it complains 'sv' is not
initialized by a constant expression. This patch addresses this
issue.
2024-10-04 06:45:29 -07:00
Casey Carter
e62c41ffb0
Conform std::iterator_traits<fmt::appender> to [iterator.traits]/1 (#4185)
* Conform `std::iterator_traits<fmt::appender>` to [iterator.traits]/1

> In addition, the types
> ```c++
> iterator_traits<I>::pointer
> iterator_traits<I>::reference
> ```
> shall be defined as the iterator’s pointer and reference types; that is, for an iterator object `a` of class type, the same type as `decltype(a.operator->())` and `decltype(*a)`, respectively. The type `iterator_traits<I>::pointer` shall be void for an iterator of class type `I` that does not support `operator->`. Additionally, in the case of an output iterator, the types
> ```c++
> iterator_traits<I>::value_type
> iterator_traits<I>::difference_type
> iterator_traits<I>::reference
> ```
> may be defined as `void`.

* Remove unnecessary member types from basic_appender

This reverts commit 1accf6c0a043fb445cbbfeefdbc1f91a08e3099f.

* Address clang-format issue
2024-10-03 09:05:14 -07:00
Francesco Cavaliere
18792893d8
Silencing Wextra-semi warning (#4188) 2024-10-03 09:00:55 -07:00
dependabot[bot]
c90bc91862
Bump actions/checkout from 4.1.6 to 4.2.0 (#4182)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](a5ac7e51b4...d632683dd7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 13:39:00 -07:00
Victor Zverovich
c95722ad62 Improve naming consistency 2024-09-29 18:17:44 -07:00
Victor Zverovich
db06b0df87 Use countl_zero in bigint 2024-09-29 17:11:22 -07:00
Victor Zverovich
b9ec48d9ca Cleanup bigint 2024-09-29 12:03:07 -07:00
Victor Zverovich
3faf6f181e Add min_of/max_of 2024-09-29 10:24:39 -07:00
Victor Zverovich
d64b100a30 Relax constexpr 2024-09-29 10:04:57 -07:00
Victor Zverovich
ff9ee0461a Fix handling FMT_BUILTIN_TYPES 2024-09-28 09:23:48 -07:00
Victor Zverovich
1c5883bef0 Test nondeterministic conversion to format string 2024-09-28 09:16:28 -07:00
Victor Zverovich
cacc3108c5 Don't assume repeated evaluation of string literal produce the same pointer
Patch by @zygoloid (#4177).
2024-09-28 08:09:33 -07:00
Justin Riddell
fade652ade
Require clang >=15 for _BitInt support (#4176)
For appleclang, fixes issue #4173
2024-09-26 16:10:51 -07:00
Cameron Angus
96dca569a1
Module linkage fixes for shared build (#4169)
* Mark some in-class defined member functions as explicitly inline/constexpr, to avoid missing external symbols when using fmt module with shared build due to modules not defaulting to implicit inline.

* Switch constexpr to inline for context::arg(string_view).
NOTE: Looks as if basic_format_args::get(string_view) could probably be made constexpr instead, but sticking with minimal change approach.

* Work around apparent non-conformance of older MSVC compilers.

* Switch format_int::str() from constexpr to inline to satisfy libstdc++ std::string constexpr limitations.

* Replace usages of macros for constexpr/inline with keywords.

* Fix for locations requiring C++14 constexpr.

* Further minor constexpr tweaks.

* Apply clang format
2024-09-26 07:53:55 -07:00
Victor Zverovich
891c9a73ae Cleanup format API 2024-09-22 15:52:55 -07:00
Victor Zverovich
9282222b7d Export more 2024-09-22 15:10:58 -07:00
Victor Zverovich
e5b20ff0d0 Deprecate detail::locale_ref 2024-09-22 10:44:38 -07:00
Victor Zverovich
ff92223549 Simplify locale handling 2024-09-22 10:21:19 -07:00
Victor Zverovich
80c4d42c66 Cleanup format.h 2024-09-22 08:20:26 -07:00
Victor Zverovich
3b70966df5 Add width and alignment support to error_code 2024-09-21 07:58:03 -07:00
Victor Zverovich
05226c4bd9 Remove type_identity 2024-09-20 19:13:09 -07:00
Victor Zverovich
c283b458a5 Cleanup format.h 2024-09-20 19:00:23 -07:00
Paulo Assis
fe79932c26
Fix conversion warning on chrono.h (#4170)
* Fix conversion warning on chrono.h

warning: conversion from 'time_t' {aka 'long long int'} to 'long int' may change value [-Wconversion]

* Changing write_utc_offset to accept a long long instead of the static_cast as requested..
2024-09-20 16:47:27 -07:00
Victor Zverovich
23fcf1942a Apply clang-format 2024-09-20 09:34:10 -07:00
Victor Zverovich
3f296e3d4a Workaround clang-format nonsense 2024-09-20 09:05:59 -07:00
Victor Zverovich
a197a994c5 Add member format_as for std 2024-09-20 08:49:49 -07:00
Victor Zverovich
6d43c755bc
Fix a typo 2024-09-19 10:49:11 -07:00
Vladislav Shchapov
1f87b1c58d
Use fmt::formatter specialization for std::reference_wrapper to avoid undefined behavior (#4164)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-19 10:34:33 -07:00
Khanh H
ed8f8be70d
More chrono padding (#4161)
* Add padding modifier to day of year, duration's remains unpadded
* Add padding modifier for %m, %Y
2024-09-18 09:27:45 -07:00
Victor Zverovich
55a0a9cd62 Cleanup pragma detection 2024-09-17 19:46:30 -07:00
Victor Zverovich
5c926d9ff9 Remove FMT_UNCHECKED_ITERATOR 2024-09-17 19:40:29 -07:00
Victor Zverovich
8b024662d4 Remove unnecessary inheritance 2024-09-17 19:31:55 -07:00
Victor Zverovich
2f1424be90 Simplify handling of arrays 2024-09-17 19:05:43 -07:00
Victor Zverovich
239aa6911b Remove unwrap_named_arg 2024-09-16 20:46:47 -07:00
Victor Zverovich
497df6db61 Remove formattable 2024-09-16 20:32:27 -07:00
Victor Zverovich
a25e594f6a Remove range_mapper 2024-09-16 20:24:30 -07:00
Victor Zverovich
503dff93ec Simplify has_formatter 2024-09-16 20:15:54 -07:00
Victor Zverovich
3374a95b50 Simplify has_formatter 2024-09-16 20:08:52 -07:00
Victor Zverovich
0e62e5dc7c Simplify has_formatter 2024-09-16 19:53:31 -07:00
Victor Zverovich
7ce013971b Sync value ctors and type mapper 2024-09-16 19:23:08 -07:00
Yedidya Feldblum
07e70151d5 format std::reference_wrapper 2024-09-16 19:05:18 -07:00
Victor Zverovich
4197727712 Improve handling of unformattable args 2024-09-16 18:52:18 -07:00
Victor Zverovich
527e98e3f8 Remove unformattable 2024-09-15 17:28:27 -07:00
Victor Zverovich
8a19b2db77 arg_mapper -> type_mapper 2024-09-15 17:09:49 -07:00
Victor Zverovich
e97df46ae1 Cleanup type mapping 2024-09-15 16:18:21 -07:00
Victor Zverovich
39f1e0903a Remove FMT_MAP_API 2024-09-15 10:52:36 -07:00
Victor Zverovich
d832830f60 Cleanup type mapping 2024-09-15 09:58:09 -07:00
Victor Zverovich
b329ff194f Always detect encoding on Windows 2024-09-15 08:52:33 -07:00
Victor Zverovich
2af403ce64 Simplify type mapping 2024-09-14 21:21:49 -07:00
Victor Zverovich
b7513b1d00 Simplify type mapping 2024-09-14 20:23:42 -07:00
Victor Zverovich
761d35f763 Cleanup format_as handling 2024-09-14 19:34:58 -07:00
Victor Zverovich
545dc4148a Add value ctor taking name_arg 2024-09-14 10:33:15 -07:00
Victor Zverovich
3f5e45dd33 Simplify handling of _BitInt 2024-09-14 10:15:36 -07:00
Victor Zverovich
2e3b6fbd9f Remove redundant check 2024-09-14 09:48:33 -07:00
Victor Zverovich
a0328e1f9f Improve error reporting 2024-09-14 09:39:41 -07:00
Victor Zverovich
de28ef5f86 Remove make_arg 2024-09-14 09:18:47 -07:00
Victor Zverovich
2d5e561a6b Cleanup argument handling 2024-09-14 08:56:04 -07:00
Victor Zverovich
6537fb439c Update changelog 2024-09-14 08:14:22 -07:00
Victor Zverovich
50aac2ac92 Add reference to iterator_traits 2024-09-14 08:07:39 -07:00
Victor Zverovich
538d8777e5 Workaround a bug in libstdc++ 2024-09-14 07:49:10 -07:00
Victor Zverovich
0335312320 Demacrify UTF-8 check 2024-09-13 18:41:10 -07:00
Victor Zverovich
463fe65f17 Cleanup FMT_COMPILE_STRING 2024-09-12 19:57:50 -07:00
Victor Zverovich
1782a6eac0 Rename pragma macros 2024-09-12 19:20:32 -07:00
Victor Zverovich
b52fb98846 Fix no locale build 2024-09-11 20:37:44 -07:00
Victor Zverovich
b6a6ec7f1c FMT_EXCEPTIONS -> FMT_USE_EXCEPTIONS 2024-09-11 19:34:12 -07:00
Victor Zverovich
89999f1672 Simplify pragma 2024-09-11 18:52:56 -07:00
Victor Zverovich
b90b4bc981 Remove FMT_STATIC_THOUSANDS_SEPARATOR in favor of FMT_USE_LOCALE 2024-09-11 18:30:05 -07:00
Victor Zverovich
a1d6f9a973 Minor cleanup 2024-09-11 17:20:20 -07:00
Victor Zverovich
689ec7a087 Cleanup 2024-09-11 16:05:34 -07:00
Victor Zverovich
28143dc99d Cleanup chrono 2024-09-11 15:41:51 -07:00
Victor Zverovich
1bde49e545 Remove FMT_USE_USER_LITERALS 2024-09-11 11:27:27 -07:00
Amin Yahyaabadi
f924d16e47 fix: pass /utf-8 only if the compiler is MSVC at build time 2024-09-11 08:21:07 -07:00
Victor Zverovich
ab8f9d5b08 Cleanup format API 2024-09-11 07:52:19 -07:00
Victor Zverovich
6f62db098a Cleanup format API 2024-09-11 07:26:11 -07:00
Victor Zverovich
ab44ee7521 Avoid shadowing 2024-09-11 07:05:45 -07:00
Victor Zverovich
0d4e7e3fee Remove old workaround 2024-09-11 06:17:47 -07:00
Victor Zverovich
8ee89546ff Remove old workaround 2024-09-10 21:06:25 -07:00
Victor Zverovich
a5deb96bf5 Update gcc version 2024-09-10 20:52:35 -07:00
Victor Zverovich
61a241f03f Cleanup 2024-09-10 20:24:57 -07:00
Victor Zverovich
ff82d8d2b5 Cleanup visit 2024-09-10 20:09:44 -07:00
Victor Zverovich
0cc20f5639 Remove iterator_t 2024-09-10 19:51:57 -07:00
Victor Zverovich
2ba6785d8f Remove unused type 2024-09-10 19:00:08 -07:00
Victor Zverovich
5644e7507c Remove unnecessary forwarding 2024-09-10 18:35:32 -07:00
Victor Zverovich
5345cfe6b3 Adjust clang-format 2024-09-10 18:24:35 -07:00
Victor Zverovich
3e9fdb3a1f Cleanup 2024-09-10 17:27:49 -07:00
Victor Zverovich
3ada4aed20 Optionally exclude Unicode data 2024-09-08 16:52:01 -07:00
Victor Zverovich
b37be85bf1 Optionally disable named arguments 2024-09-08 16:25:33 -07:00
Victor Zverovich
70643b2511 Don't use format_error if exceptions disabled 2024-09-08 15:56:36 -07:00
Victor Zverovich
967e2d177d Cleanup 2024-09-08 15:43:11 -07:00
Victor Zverovich
02c5d637c5 Cleanup 2024-09-08 11:13:44 -07:00
Victor Zverovich
047bf75c24 Cleanup 2024-09-08 10:00:25 -07:00
Victor Zverovich
2d3ba32e79 Improve debug codegen 2024-09-08 09:17:59 -07:00
Victor Zverovich
6c90b31fbd Improve debug codegen 2024-09-08 07:49:02 -07:00
Victor Zverovich
9408c2ae8c Readd support for FMT_BUILTIN_TYPES 2024-09-07 08:12:03 -07:00
Victor Zverovich
cc3ff1529d Cleanup 2024-09-06 17:05:48 -07:00
Victor Zverovich
158893b384 Cleanup 2024-09-06 13:39:03 -07:00
Victor Zverovich
f5a16a484b Cleanup 2024-09-06 12:41:53 -07:00
Victor Zverovich
cad876be4c Switch to vargs 2024-09-06 12:12:39 -07:00
Victor Zverovich
debf6f8285 Switch to vargs 2024-09-06 09:10:39 -07:00
Victor Zverovich
35f4fab4c4 Simplify value ctor 2024-09-06 08:59:43 -07:00
Victor Zverovich
ff8f324786 Minor cleanup 2024-09-06 08:47:24 -07:00
Victor Zverovich
bd48715d81 Simplify make_format_args 2024-09-06 08:15:33 -07:00
Victor Zverovich
57d6df62f7 Simplify make_format_args 2024-09-06 08:07:22 -07:00
Victor Zverovich
8ed4a9dcc1 Improve debug codegen 2024-09-06 07:51:22 -07:00
Victor Zverovich
f288f45e46 Prepare for arg_store unification 2024-09-05 19:17:18 -07:00
Vladislav Shchapov
5bf577ca58 Backport from GoogleTest: "Work around a maybe-uninitialized warning under GCC 12" (0320f517fd)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-05 09:40:55 -07:00
Vladislav Shchapov
b6de66819e Backport from GoogleTest: "Always initialize fields in MatcherBase constructors" (https://github.com/google/googletest/pull/3797)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-05 09:40:55 -07:00
Vladislav Shchapov
6870e4b06b Workaround for GCC regression: false positive null-dereference in vector.resize
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-05 09:40:55 -07:00
Vladislav Shchapov
5cdef76034 Switch to gcc-13 for C++23 tests
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-05 09:40:55 -07:00
Victor Zverovich
a2c290bc34 Suppress a bogus MSVC warning 2024-09-04 17:20:30 -07:00
Victor Zverovich
f1e3016c13 Optimize debug codegen 2024-09-04 17:10:52 -07:00
Victor Zverovich
106dc8fd64 Reduce usage of type_identity 2024-09-04 16:23:51 -07:00
Victor Zverovich
c3344e21e2 Cleanup base API 2024-09-04 15:50:53 -07:00
Victor Zverovich
5f438c967e Remove make_arg 2024-09-04 14:52:14 -07:00
Victor Zverovich
2a257798d4 Reenable FMT_BUILTIN_TYPES 2024-09-04 14:10:40 -07:00
Vladislav Shchapov
22d50c1a9c Add support formatting std::expected<void, E>
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-04 12:31:44 -07:00
Vladislav Shchapov
1cc10ab68f Make is_formattable work with const/volatile void
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-04 12:31:44 -07:00
Victor Zverovich
6aaf7f4b79 Suppress a gcc 13 warning 2024-09-04 11:43:12 -07:00
Victor Zverovich
b4d1d7f8e6 Improve debug codegen 2024-09-04 08:56:25 -07:00
Vladislav Shchapov
1e0771c70a
Fix broken CI for shared library on macos and linux (#4151)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-09-04 08:53:50 -07:00
Victor Zverovich
3df47a4677 Make is_formattable work with void 2024-09-04 07:33:56 -07:00
Cameron Angus
b4aea98b55
Small fixes for some issues with modules builds (#4152)
* Avoid module export of member function definitions.

* Do not #include intrinsics header into module purview.
2024-09-04 07:29:41 -07:00
Victor Zverovich
565461a0d3 Update MSVC workaround in compile-test 2024-09-04 06:43:05 -07:00
Victor Zverovich
e2b7238707 Cleanup format string API 2024-09-03 21:30:57 -07:00
Victor Zverovich
1e0c6cdc3b Make symbol sizes shorter 2024-09-03 20:44:37 -07:00
Victor Zverovich
a8bcf81f72 Minor cleanup 2024-09-03 18:39:46 -07:00
Victor Zverovich
15694c9a84 Workaround an MSVC bug 2024-09-03 16:12:29 -07:00
Victor Zverovich
4cae2da0d0 Workaround a clang 17 bug 2024-09-03 15:58:33 -07:00
Victor Zverovich
79e5ae919c Fix locale tests on FreeBSD 2024-09-03 12:50:03 -07:00
Victor Zverovich
894b71da85 Fix handling of _BitInt 2024-09-03 11:32:31 -07:00
Victor Zverovich
7a6a2a79ed Improve debug codegen 2024-09-02 20:29:24 -07:00
Victor Zverovich
387395fc7c Cleanup base API 2024-09-02 15:15:38 -07:00
Victor Zverovich
6a88415499 Add FMT_APPLY_VARIADIC 2024-09-02 13:59:41 -07:00
Victor Zverovich
9a2aae37d4 Cleanup base API 2024-09-02 10:01:14 -07:00
Victor Zverovich
8803768363 Cleanup base API 2024-09-02 09:11:33 -07:00
Victor Zverovich
4fa533c70e Cleanup base API 2024-09-02 09:00:44 -07:00
Victor Zverovich
d980dd7171 Cleanup base API 2024-09-02 07:50:46 -07:00
Victor Zverovich
4eed488c66 Cleanup base API 2024-09-02 07:17:21 -07:00
Victor Zverovich
a6ecd25b80 Improve debug codegen 2024-09-02 06:54:45 -07:00
Victor Zverovich
9f29345ea0 Simplify mapped_type_constant 2024-09-02 06:41:44 -07:00
Victor Zverovich
4986b4c0ef Simplify arg_mapper 2024-09-01 21:59:39 -07:00
Victor Zverovich
a5f4d9820c Simplify arg_mapper 2024-09-01 21:47:41 -07:00
Victor Zverovich
bc3af51272 Reduce the number of instantiations 2024-09-01 19:54:09 -07:00
Victor Zverovich
60740b7c24 Cleanup base API 2024-09-01 19:35:00 -07:00
Victor Zverovich
9ef160d309 Cleanup base API 2024-09-01 19:02:47 -07:00
Victor Zverovich
0b80978c27 Cleanup base API 2024-09-01 18:31:41 -07:00
Victor Zverovich
4f39d88650 Cleanup base API 2024-09-01 18:24:24 -07:00
Victor Zverovich
a86b1acf6a Add mapped_t 2024-09-01 17:48:29 -07:00
Victor Zverovich
c9ef07bc4e Minor cleanup 2024-09-01 17:34:47 -07:00
Victor Zverovich
8c4cfab57a Detemplatize parse 2024-09-01 14:32:55 -07:00
Victor Zverovich
7e3aa6d982 Minor cleanup 2024-09-01 14:17:38 -07:00
Victor Zverovich
7c66216008 Minor cleanup 2024-09-01 12:53:09 -07:00
Victor Zverovich
1416edabbb Cleanup base API 2024-09-01 12:06:40 -07:00
dependabot[bot]
d4aeca9922
Bump actions/upload-artifact from 4.3.3 to 4.4.0 (#4141)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.4.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65462800fd...50769540e7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-01 11:15:43 -07:00
dependabot[bot]
eee93ddffa
Bump github/codeql-action from 3.25.11 to 3.26.6 (#4142)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.11 to 3.26.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b611370bb5...4dd16135b6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-01 11:15:36 -07:00
Victor Zverovich
b310a0d48b Simplify parse_format_string 2024-09-01 11:09:26 -07:00
Victor Zverovich
985c3399d1 Make map static 2024-09-01 09:18:47 -07:00
Victor Zverovich
4a55b0d5fd Remove duplicate error in compile-time checks 2024-09-01 09:03:34 -07:00
Victor Zverovich
64a6c84592 basic_format_parse_context -> parse_context 2024-09-01 08:09:37 -07:00
Victor Zverovich
66920feeee Improve compile-time checks 2024-09-01 07:49:35 -07:00
Victor Zverovich
f4dad85c3a Improve handling of named arguments in compile-time checks 2024-09-01 07:07:14 -07:00
Victor Zverovich
db4becabed Reduce template instantiations 2024-08-31 20:29:58 -07:00
Victor Zverovich
fec2cc7af1 Improve handling of named arguments 2024-08-31 19:55:04 -07:00
Victor Zverovich
621e9c17c5 Clarify why we have TYPE in native_formatter 2024-08-31 17:50:21 -07:00
Victor Zverovich
bca7040556 Simplify compile-time checks 2024-08-31 15:01:25 -07:00
Victor Zverovich
8c4b17fe64 Simplify compile-time checks 2024-08-31 14:49:59 -07:00
Victor Zverovich
516a2e2049 Cleanup FMT_STRING 2024-08-31 14:13:57 -07:00
Victor Zverovich
6797f0c39a Cleanup compile-time checks 2024-08-31 11:26:27 -07:00
Victor Zverovich
db496b47c1 Remove old gcc hack 2024-08-31 09:22:49 -07:00
Victor Zverovich
8eda3c8e90 Cleanup compile-time check 2024-08-31 08:38:53 -07:00
Victor Zverovich
53316903e6 Move string_literal to format.h 2024-08-31 07:08:35 -07:00
Victor Zverovich
8a484ad577 Minor cleanup 2024-08-30 20:53:54 -07:00
Victor Zverovich
b446cc9e67 fwrite_fully -> fwrite_all 2024-08-30 18:43:56 -07:00
Victor Zverovich
0204dd359d Fix _BitInt formatter 2024-08-30 18:30:20 -07:00
Victor Zverovich
d8876b7787 Minor cleanup 2024-08-30 16:17:07 -07:00
Victor Zverovich
c0fab5e2f7 Reject modernity, embrace tradition 2024-08-30 11:26:29 -07:00
Victor Zverovich
64313e915c Move redundant initialization to compile time 2024-08-30 10:51:35 -07:00
Victor Zverovich
8e3da9da2c Improve binary size 2024-08-30 10:27:03 -07:00
Victor Zverovich
2a2f73f7c1 Improve binary size 2024-08-29 19:16:54 -07:00
Victor Zverovich
6dd9194abd Simplify format_to_result 2024-08-29 18:35:42 -07:00
Victor Zverovich
a017bba062 Minor cleanup 2024-08-29 18:22:09 -07:00
Victor Zverovich
5eb023cd56 Improve binary size 2024-08-29 17:31:30 -07:00
Victor Zverovich
f213d83306 Disable locale more 2024-08-29 16:35:15 -07:00
Victor Zverovich
b3ccc2d210 Disable locale more 2024-08-29 15:08:43 -07:00
Victor Zverovich
7477dda28d Simplify is_utf8_enabled 2024-08-29 14:39:26 -07:00
Victor Zverovich
e582d377c2 Simplify locale handling 2024-08-29 14:19:33 -07:00
Victor Zverovich
cd8d01d8cd Minor cleanup 2024-08-29 11:41:43 -07:00
Victor Zverovich
377cf203e3 Add opt out for built-in types 2024-08-29 11:21:29 -07:00
Justin Riddell
5a0a37340c
Add support for _BitInt on clang (#4072)
Issue #4007
Make _BitInt up to 128bits formattable
Note, libstdc++ is_signed doesn't work with _BitInt (so use own)
2024-08-28 18:57:52 -07:00
torsten48
bbf8b3bd01
insert else branch to avoid unreachable code warning (#4130)
at least MSC caused warning C4702: unreachable code
2024-08-28 16:43:12 -07:00
Justin Riddell
a3f3f2ec9a
Fix gcc 8.1 - 8.3 bug and compilation (#4131)
Fixes issue #4129
2024-08-28 11:25:39 -07:00
Maxwell
e3676ca309
Change std::copy to detail::copy in chrono to fix MSVC compile errors (#4132) 2024-08-28 08:25:40 -07:00
Victor Zverovich
0379bf3a5d Workaround -Wstringop-overflow 2024-08-24 07:56:09 -07:00
Anthony VH
c59ee969f3
Improve compile-time formatting (#4127) 2024-08-21 12:02:47 -07:00
Victor Zverovich
1a79bbfa83 Cleanup chrono formatting 2024-08-18 12:17:45 -07:00
Victor Zverovich
89af1ad77d Cleanup chrono formatting 2024-08-18 11:55:33 -07:00
Victor Zverovich
0e741e0daa Minor cleanup 2024-08-18 10:35:01 -07:00
Victor Zverovich
d1acc667c1 Minor cleanup 2024-08-18 09:33:29 -07:00
Victor Zverovich
4fb7008c90 Cleanup duration cast 2024-08-18 08:33:26 -07:00
Victor Zverovich
589898e28b Fix %S doc 2024-08-18 07:40:28 -07:00
Victor Zverovich
62382e3650 Test full exponent range 2024-08-18 06:47:04 -07:00
Victor Zverovich
94b8bc8eae Add an experimental writer API 2024-08-17 09:54:09 -07:00
Victor Zverovich
020af729dd Simplify ostream 2024-08-17 08:38:10 -07:00
cdzhan
fb07b37c5b
Prioritize using the header files of self (#4116) 2024-08-13 10:50:49 -07:00
Victor Zverovich
3135421257 Minor cleanup 2024-08-12 07:59:42 -07:00
Victor Zverovich
993f56cff6 Make sign a proper enum class 2024-08-11 13:49:57 -07:00
Victor Zverovich
c6c830e203 Make align a proper enum class 2024-08-11 11:07:18 -07:00
Victor Zverovich
b906c321f0 Get rid of bit fields 2024-08-11 10:28:09 -07:00
Victor Zverovich
f8c0c8ee78 Cleanup public API 2024-08-11 07:29:17 -07:00
Roberto Turrado Camblor
c71d03fcb0
Make support/python/mkdocstrings_handlers/cxx/__init__.py PEP 8 compliant (2 of 2) (#4115)
* Change indents to 4 spaces.

* Run isort.

* Remove one extra space on the left hand side of each assignment at p.communicate's input.

* Remove 'from __future__ import annotations'.
This requires changing a 'list[]' into a 'List[]'.

We had previously added this import because the code was making use of operator '|'.
But that is no longer true, so the import shouldn't be needed.
2024-08-11 07:24:50 -07:00
Victor Zverovich
50a8c3e9bf Reduce format specs size 2024-08-10 09:34:35 -07:00
Victor Zverovich
98314319ad Fix ambiguous overload 2024-08-09 15:01:40 -07:00
Victor Zverovich
0ce49aeb4a Add a test case 2024-08-09 10:18:11 -07:00
Victor Zverovich
bf870ae3d1 Fix back_inserter lookup for non-std containers 2024-08-09 07:10:15 -07:00
Roberto Turrado Camblor
c98518351e
Make support/python/mkdocstrings_handlers/cxx/__init__.py PEP 8 compliant (1 of 2) (#4110)
* Make support/python/mkdocstrings_handlers/cxx/__init__.py PEP compliant.

* Rollback minor change in __init__ signature.

* Rollback minor change in __init__ signature.

* Fix previous commit.

* Add 'from __future__ import annotations' to fix Python 3.8 error when using operator '|'.

* Change doxyxml2html([n]) to doxyxml2html(list(n)) as suggested by Victor.
Change convert_type return type to Optional[str].
2024-08-08 15:22:41 -07:00
Hugo Sales
9f0c0c468b
Add 'n' specifier for tuple and pair (#4107) 2024-08-05 14:56:44 -07:00
Victor Zverovich
9f269062a7 Simplify default formatter 2024-08-05 14:24:07 -07:00
Victor Zverovich
15f939c3de Improve handling of dynamic specs 2024-08-04 09:25:50 -07:00
Victor Zverovich
928a07bb04 Simplify handling of dynamic specs 2024-08-04 09:09:01 -07:00
Victor Zverovich
7891699737 Simplify handling of dynamic specs 2024-08-04 08:47:07 -07:00
Victor Zverovich
58aba5a3de Deprecate append instantiation 2024-08-03 11:55:25 -07:00
Vladislav Shchapov
5ee14d3508
Reintroduce constexpr fmt::formatted_size for C++20 (#4103)
* Reintroduce constexpr fmt::formatted_size for C++20

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

* Disable constexpr fmt::formatted_size on Visual Studio 2019

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

---------

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-08-03 07:13:32 -07:00
Victor Zverovich
b9c0e4dd82 Improve spec parsing 2024-08-02 11:57:02 -07:00
Victor Zverovich
8445327c84 Simplify spec handling 2024-08-01 19:59:21 -07:00
Victor Zverovich
8a06cee826 Optimize shortest float formatting 2024-08-01 18:54:56 -07:00
Victor Zverovich
1db2274966 Use us if Unicode is disabled 2024-08-01 10:24:43 -07:00
dependabot[bot]
34ead4b39e
Bump msys2/setup-msys2 from 2.23.0 to 2.24.0 (#4098)
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.23.0 to 2.24.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](d0e80f58df...5df0ca6cbf)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-01 08:32:46 -07:00
dependabot[bot]
3bf26009e4
Bump ossf/scorecard-action from 2.3.3 to 2.4.0 (#4099)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.3 to 2.4.0.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](dc50aa9510...62b2cac7ed)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-01 08:32:20 -07:00
Vladislav Shchapov
d326c7298a
Fix conversion a surrogate pair (#4095)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-07-30 07:27:11 -07:00
Victor Zverovich
6e462b89aa Get rid of std::copy 2024-07-29 15:58:05 -07:00
Vladislav Shchapov
aff640c32f
Make fmt::appender implement std::output_iterator concept (#4093)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2024-07-29 09:18:20 -07:00
Victor Zverovich
e23fb6a8b4 Apply clang-format 2024-07-29 08:20:58 -07:00
Victor Zverovich
16b3542f7e Remove float_specs 2024-07-27 12:28:21 -07:00
Victor Zverovich
29d7e58059 Remove float_format 2024-07-27 12:07:19 -07:00
Victor Zverovich
919f7c5e7f Reduce float_specs usage 2024-07-27 11:38:55 -07:00
Victor Zverovich
a80d668a52 Diagnose invalid precision 2024-07-27 10:41:54 -07:00
Victor Zverovich
707d7d923a Apply coding conventions 2024-07-27 09:00:25 -07:00
Victor Zverovich
de6ed8df8b Test alignment 2024-07-26 12:09:47 -07:00
Victor Zverovich
ffdc3fdbd9 Align digits table 2024-07-24 18:43:37 -07:00
Victor Zverovich
0c02813791 Fix doc build 2024-07-24 13:40:54 -07:00
Victor Zverovich
f8581bcecf Add redirect page 2024-07-24 12:21:44 -07:00
Cameron Angus
31b3c325f6
Mark namespace scope constexpr variable 'buffer_size' inline. (#4084)
* Mark namespace scope constexpr variable 'buffer_size' inline.

* Use provided macro for inline variable.
2024-07-24 09:58:38 -07:00
Cameron Angus
52b32081f9
Wrap private module fragment content within conditional extern "C++", to match declarations. (#4083) 2024-07-24 06:25:23 -07:00
Victor Zverovich
0b0b09f401 Constrain format_uint 2024-07-23 06:30:35 -07:00
Victor Zverovich
4173a6315a Improve format_decimal 2024-07-22 17:24:56 -07:00
Victor Zverovich
4239dfe081 Simplify format_decimal 2024-07-22 17:00:16 -07:00
Victor Zverovich
ba36a04811 Remove counting_iterator 2024-07-22 16:24:13 -07:00
Victor Zverovich
f6b4a23b83 Unbloat chrono 2024-07-22 15:46:58 -07:00
Victor Zverovich
42d3d703b5 Remove the commenting attempt 2024-07-22 13:48:56 -07:00
Victor Zverovich
9fcd9c4c12 Remove all warning suppressions 2024-07-22 12:41:12 -07:00
Victor Zverovich
7f157dca0a Workaround gcc stringop-overflow bug 2024-07-22 11:31:35 -07:00
Victor Zverovich
524ca1c715 Improve parsing 2024-07-21 09:57:18 -07:00
Victor Zverovich
bdc45eef76 Simplify on_text 2024-07-21 08:31:03 -07:00
Victor Zverovich
439b6d7212 Reenable print optimization 2024-07-21 08:05:07 -07:00
Victor Zverovich
3cc32fdc8b Mark more formatters nonlocking 2024-07-21 08:00:34 -07:00
Victor Zverovich
0c9fce2ffe Update version 2024-07-20 07:17:38 -07:00
Victor Zverovich
b47d662e71 Update changelog 2024-07-20 07:14:03 -07:00
Victor Zverovich
e84297f255 Bump version 2024-07-20 07:00:12 -07:00
Victor Zverovich
0ad234ad13 Update changelog 2024-07-20 06:57:55 -07:00
Victor Zverovich
de684ef776 Make appender compatible with fill 2024-07-19 15:21:57 -07:00
Victor Zverovich
447c6cbf44 Update changelog 2024-07-19 13:38:27 -07:00
Victor Zverovich
bc8d32e964 Update changelog 2024-07-18 06:37:02 -07:00
Victor Zverovich
0f87d6ffa6 Improve sign processing 2024-07-17 16:13:27 -07:00
Victor Zverovich
808ea0191a Cleanup test 2024-07-17 06:59:24 -07:00
Victor Zverovich
55e76e6c20 Update check-commits script 2024-07-17 06:58:37 -07:00
Victor Zverovich
8757f1f8d6 Add a script to test multiple commits 2024-07-16 06:27:01 -07:00
Victor Zverovich
9228f349a5 Inline visit 2024-07-14 15:34:53 -07:00
Victor Zverovich
e10643add2 Add a perf-sanity test 2024-07-14 14:17:39 -07:00
Victor Zverovich
f29a7e7970 Don't use memcpy in append 2024-07-14 13:02:21 -07:00
Victor Zverovich
f97deb0d7d Minor cleanup 2024-07-14 11:14:49 -07:00
Victor Zverovich
3541353512 Apply minor optimization 2024-07-14 09:52:44 -07:00
Justin Riddell
5ef93a9f80
Expand FMT_FORMAT_AS to include implicit conversions (#4055)
Allows (for example) types convertible to std::string_view to inherit
from the fmt::formatter<fmt::string_view> to work etc.
2024-07-14 09:51:49 -07:00
Victor Zverovich
c9102619da Avoid extra reserve 2024-07-14 08:41:35 -07:00
Victor Zverovich
58d792b6d3 Apply minor optimizations 2024-07-14 07:05:18 -07:00
Victor Zverovich
25adca5666 Remove redundant overload 2024-07-13 13:07:57 -07:00
Victor Zverovich
1408f1824d Simplify iterator detection 2024-07-13 11:11:47 -07:00
Tor Shepherd
3fe4641d3a
Add 2 more constexprs to fix compile error (#4065) 2024-07-13 08:23:49 -07:00
Victor Zverovich
33e7ed1eb5 Improve handling of back_insert_iterator that writes into a buffer 2024-07-13 07:56:11 -07:00
zyctree
6a192f8d34
Fix broken links in README.md (#4066) 2024-07-12 11:21:35 -07:00
Victor Zverovich
92cdbbae06
Update api.md 2024-07-10 15:50:10 -07:00
Justin Riddell
13038f37e8
Support printing (const) volatile void* (#4056)
Fixes #4049
2024-07-10 08:35:06 -07:00
Victor Zverovich
6725225750 Update changelog 2024-07-09 13:22:56 -07:00
Justin Riddell
e60ff504ea
Fix usage with std::generator (#4057)
Fixes #4053
2024-07-09 08:46:34 -07:00
Victor Zverovich
ccea338070
Update lint.yml 2024-07-08 15:19:57 -07:00
Victor Zverovich
92227c77a4 Improve support for non-POSIX platforms more 2024-07-08 14:00:00 -07:00
Victor Zverovich
486838f26f Improve support for non-POSIX platforms 2024-07-08 11:18:44 -07:00
Victor Zverovich
a43391199f Update changelog 2024-07-07 10:05:03 -07:00
Victor Zverovich
7a8b54a0ef Don't confuse Glib::ustring with std::string 2024-07-06 13:27:06 -07:00
75 changed files with 8880 additions and 7703 deletions

View File

@ -6,3 +6,10 @@ IndentPPDirectives: AfterHash
IndentCaseLabels: false IndentCaseLabels: false
AlwaysBreakTemplateDeclarations: false AlwaysBreakTemplateDeclarations: false
DerivePointerAlignment: false DerivePointerAlignment: false
AllowShortCaseLabelsOnASingleLine: true
QualifierAlignment: Left
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: true
AcrossComments: true
AlignCaseColons: false

4
.clang-tidy Normal file
View File

@ -0,0 +1,4 @@
Checks: modernize-use-trailing-return-type
CheckOptions:
- key: modernize-use-trailing-return-type.TransformLambdas
value: none

View File

@ -10,20 +10,22 @@ jobs:
steps: steps:
- name: Build fuzzers - name: Build fuzzers
id: build id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a
with: with:
oss-fuzz-project-name: 'fmt' oss-fuzz-project-name: 'fmt'
dry-run: false dry-run: false
language: c++ language: c++
- name: Run fuzzers - name: Run fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a
with: with:
oss-fuzz-project-name: 'fmt' oss-fuzz-project-name: 'fmt'
fuzz-seconds: 300 fuzz-seconds: 300
dry-run: false dry-run: false
language: c++ language: c++
- name: Upload crash - name: Upload crash
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: failure() && steps.build.outcome == 'success' if: failure() && steps.build.outcome == 'success'
with: with:
name: artifacts name: artifacts

View File

@ -7,11 +7,10 @@ permissions:
jobs: jobs:
build: build:
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken. runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Add Ubuntu mirrors - name: Add Ubuntu mirrors
run: | run: |
@ -25,7 +24,7 @@ jobs:
run: | run: |
sudo apt update sudo apt update
sudo apt install doxygen sudo apt install doxygen
pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1 pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
cmake -E make_directory ${{runner.workspace}}/build cmake -E make_directory ${{runner.workspace}}/build
# Workaround https://github.com/actions/checkout/issues/13: # Workaround https://github.com/actions/checkout/issues/13:
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"

View File

@ -8,48 +8,21 @@ on:
permissions: permissions:
contents: read contents: read
pull-requests: write
jobs: jobs:
format_code: format_code:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install clang-format - name: Install clang-format
uses: aminya/setup-cpp@290824452986e378826155f3379d31bce8753d76 # v0.37.0 run: |
with: wget https://apt.llvm.org/llvm.sh
clangformat: 17.0.5 sudo bash ./llvm.sh 21
sudo apt install clang-format-21
- name: Run clang-format - name: Run clang-format
id: clang_format
run: | run: |
find include src -name '*.h' -o -name '*.cc' | xargs clang-format -i -style=file -fallback-style=none find include src -name '*.h' -o -name '*.cc' | \
git diff | tee fmt.patch xargs clang-format-21 -i -style=file -fallback-style=none
if [ -s fmt.patch ]; then git diff --exit-code
exit 1
fi
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
if: failure() && steps.clang_format.outcome == 'failure'
with:
script: |
const fs = require('fs');
const patch = fs.readFileSync('fmt.patch', { encoding: 'utf8' });
const comment = `clang-format 17.0.5 found issues in the formatting in your code:
<details>
<summary>
View the diff from clang-format:
</summary>
\`\`\`diff
${patch}
\`\`\`
</details>
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

View File

@ -7,85 +7,143 @@ permissions:
jobs: jobs:
build: build:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
cxx: [g++-4.8, g++-10, clang++-9] cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11]
build_type: [Debug, Release] build_type: [Debug, Release]
std: [11] std: [11]
shared: [""]
include: include:
- cxx: g++-4.8 - cxx: g++-4.9
install: sudo apt install g++-4.8 - cxx: clang++-3.6
- cxx: g++-8 - cxx: g++-11
build_type: Debug build_type: Debug
std: 14 std: 14
install: sudo apt install g++-8 install: sudo apt install g++-11
- cxx: g++-8 - cxx: g++-11
build_type: Debug
std: 17
install: sudo apt install g++-8
- cxx: g++-9
build_type: Debug
std: 17
- cxx: g++-10
build_type: Debug build_type: Debug
std: 17 std: 17
- cxx: g++-11 - cxx: g++-11
build_type: Debug build_type: Debug
std: 20 std: 20
install: sudo apt install g++-11 install: sudo apt install g++-11
- cxx: clang++-8 - cxx: g++-13
build_type: Release
std: 23
install: sudo apt install g++-13
shared: -DBUILD_SHARED_LIBS=ON
- cxx: clang++-11
build_type: Debug build_type: Debug
std: 17 std: 17
cxxflags: -stdlib=libc++ cxxflags: -stdlib=libc++
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev install: sudo apt install clang-11 libc++-11-dev libc++abi-11-dev
- cxx: clang++-9 - cxx: clang++-11
install: sudo apt install clang-9 install: sudo apt install clang-11
- cxx: clang++-9 - cxx: clang++-11
build_type: Debug build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17 std: 17
install: sudo apt install clang-9 install: sudo apt install clang-11
- cxx: clang++-11 - cxx: clang++-14
build_type: Debug build_type: Debug
std: 20 std: 20
- cxx: clang++-11 - cxx: clang++-14
build_type: Debug build_type: Debug
std: 20 std: 20
cxxflags: -stdlib=libc++ cxxflags: -stdlib=libc++
install: sudo apt install libc++-11-dev libc++abi-11-dev install: sudo apt install libc++-14-dev libc++abi-14-dev
- shared: -DBUILD_SHARED_LIBS=ON
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone - name: Set timezone
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' run: sudo timedatectl set-timezone 'Europe/Kyiv'
- name: Add repositories for older GCC - name: Install GCC 4.9
run: | run: |
# Below two repos provide GCC 4.8, 5.5 and 6.4 sudo apt update
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main' sudo apt install libatomic1 libc6-dev libgomp1 libitm1 libmpc3
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe' # https://launchpad.net/ubuntu/xenial/amd64/g++-4.9/4.9.3-13ubuntu2
# Below two repos additionally update GCC 6 to 6.5 wget --no-verbose \
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main' http://launchpadlibrarian.net/230069137/libmpfr4_3.1.3-2_amd64.deb \
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe' http://launchpadlibrarian.net/253728424/libasan1_4.9.3-13ubuntu2_amd64.deb \
if: ${{ matrix.cxx == 'g++-4.8' }} http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/253728426/libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728432/libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728314/gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/253728399/cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728404/gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728401/g++-4.9_4.9.3-13ubuntu2_amd64.deb
sudo dpkg -i \
libmpfr4_3.1.3-2_amd64.deb \
libasan1_4.9.3-13ubuntu2_amd64.deb \
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
g++-4.9_4.9.3-13ubuntu2_amd64.deb
if: ${{ matrix.cxx == 'g++-4.9' }}
- name: Install Clang 3.6
run: |
sudo apt update
sudo apt install libtinfo5
# https://code.launchpad.net/ubuntu/xenial/amd64/clang-3.6/1:3.6.2-3ubuntu2
wget --no-verbose \
http://launchpadlibrarian.net/230019046/libffi6_3.2.1-4_amd64.deb \
http://launchpadlibrarian.net/445346109/libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346128/libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346113/libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346131/libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346022/libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/254405108/libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
http://launchpadlibrarian.net/254405097/libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
http://launchpadlibrarian.net/254405101/libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/254405091/clang-3.6_3.6.2-3ubuntu2_amd64.deb
sudo dpkg -i \
libffi6_3.2.1-4_amd64.deb \
libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
clang-3.6_3.6.2-3ubuntu2_amd64.deb
if: ${{ matrix.cxx == 'clang++-3.6' }}
- name: Add repositories for newer GCC - name: Add repositories for newer GCC
run: | run: |
sudo apt-add-repository ppa:ubuntu-toolchain-r/test sudo apt-add-repository ppa:ubuntu-toolchain-r/test
if: ${{ matrix.cxx == 'g++-11' }} if: ${{ matrix.cxx == 'g++-13' }}
- name: Add Ubuntu mirrors - name: Add Ubuntu mirrors
run: | run: |
# Github Actions caching proxy is at times unreliable # GitHub Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048 # see https://github.com/actions/runner-images/issues/7048.
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt mirrors=/etc/apt/mirrors.txt
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | \
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list sudo tee $mirrors
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append $mirrors
sudo sed -i \
"s~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:$mirrors~" \
/etc/apt/sources.list
- name: Create Build Environment - name: Create build environment
run: | run: |
sudo apt update sudo apt update
${{matrix.install}} ${{matrix.install}}
@ -98,10 +156,12 @@ jobs:
CXX: ${{matrix.cxx}} CXX: ${{matrix.cxx}}
CXXFLAGS: ${{matrix.cxxflags}} CXXFLAGS: ${{matrix.cxxflags}}
run: | run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \ cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \
${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE
- name: Build - name: Build
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build

View File

@ -9,22 +9,23 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [macos-13, macos-14] os: [macos-14]
build_type: [Debug, Release] build_type: [Debug, Release]
std: [11, 17, 20] std: [11, 17, 20, 23]
exclude: shared: [""]
- { os: macos-13, std: 11 }
- { os: macos-13, std: 17 }
include: include:
- shared: -DBUILD_SHARED_LIBS=ON - os: macos-14
std: 23
build_type: Release
shared: -DBUILD_SHARED_LIBS=ON
runs-on: '${{ matrix.os }}' runs-on: '${{ matrix.os }}'
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone - name: Set timezone
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' run: sudo systemsetup -settimezone 'Europe/Minsk'
- name: Select Xcode 14.3 (macOS 13) - name: Select Xcode 14.3 (macOS 13)
run: sudo xcode-select -s "/Applications/Xcode_14.3.app" run: sudo xcode-select -s "/Applications/Xcode_14.3.app"

View File

@ -29,12 +29,12 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@ -52,7 +52,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@ -60,6 +60,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11 uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -10,27 +10,18 @@ jobs:
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
strategy: strategy:
matrix: matrix:
# windows-2019 has MSVC 2019 installed;
# windows-2022 has MSVC 2022 installed: # windows-2022 has MSVC 2022 installed:
# https://github.com/actions/virtual-environments. # https://github.com/actions/virtual-environments.
os: [windows-2019] os: [windows-2022]
platform: [Win32, x64] platform: [Win32, x64]
toolset: [v140, v141, v142] toolset: [v142]
standard: [14, 17, 20] standard: [14, 17, 20]
shared: ["", -DBUILD_SHARED_LIBS=ON] shared: ["", -DBUILD_SHARED_LIBS=ON]
build_type: [Debug, Release] build_type: [Debug, Release]
exclude: exclude:
- { toolset: v140, standard: 17 }
- { toolset: v140, standard: 20 }
- { toolset: v141, standard: 14 }
- { toolset: v141, standard: 20 }
- { toolset: v142, standard: 14 } - { toolset: v142, standard: 14 }
- { platform: Win32, toolset: v140 }
- { platform: Win32, toolset: v141 }
- { platform: Win32, standard: 14 } - { platform: Win32, standard: 14 }
- { platform: Win32, standard: 20 } - { platform: Win32, standard: 20 }
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON } - { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON } - { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
include: include:
@ -41,10 +32,10 @@ jobs:
standard: 20 standard: 20
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone - name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time" run: tzutil /s "FLE Standard Time"
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
@ -81,14 +72,14 @@ jobs:
sys: [ mingw64, ucrt64 ] sys: [ mingw64, ucrt64 ]
steps: steps:
- name: Set timezone - name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time" run: tzutil /s "FLE Standard Time"
shell: cmd shell: cmd
- uses: msys2/setup-msys2@d0e80f58dffbc64f6a3a1f43527d469b4fc7b6c8 # v2.23.0 - uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638 # v2.28.0
with: with:
release: false release: false
msystem: ${{matrix.sys}} msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Configure - name: Configure
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
env: { LDFLAGS: -fuse-ld=lld } env: { LDFLAGS: -fuse-ld=lld }

3
.gitignore vendored
View File

@ -3,11 +3,14 @@
*.xcodeproj *.xcodeproj
*~ *~
.vscode/ .vscode/
.cache/
.vs/
/CMakeScripts /CMakeScripts
/Testing /Testing
/_CPack_Packages /_CPack_Packages
/install_manifest.txt /install_manifest.txt
CMakeCache.txt CMakeCache.txt
CMakeUserPresets.json
CMakeFiles CMakeFiles
CPack*.cmake CPack*.cmake
CTestTestfile.cmake CTestTestfile.cmake

View File

@ -9,7 +9,9 @@ endif ()
# or if it is the master project. # or if it is the master project.
if (NOT DEFINED FMT_MASTER_PROJECT) if (NOT DEFINED FMT_MASTER_PROJECT)
set(FMT_MASTER_PROJECT OFF) set(FMT_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # NOTE: source vs current_source detection is unreliable
# this heuristic is more generally applicable esp w.r.t FetchContent
if (NOT DEFINED PROJECT_NAME)
set(FMT_MASTER_PROJECT ON) set(FMT_MASTER_PROJECT ON)
message(STATUS "CMake version: ${CMAKE_VERSION}") message(STATUS "CMake version: ${CMAKE_VERSION}")
endif () endif ()
@ -27,12 +29,15 @@ endfunction()
# DEPRECATED! Should be merged into add_module_library. # DEPRECATED! Should be merged into add_module_library.
function(enable_module target) function(enable_module target)
if (MSVC) if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
target_compile_options(${target} set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
PRIVATE /interface /ifcOutput ${BMI} file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
INTERFACE /reference fmt=${BMI}) target_compile_options(${target}
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) PRIVATE /interface /ifcOutput ${BMI}
set_source_files_properties(${BMI} PROPERTIES GENERATED ON) INTERFACE /reference fmt=${BMI})
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif()
endif () endif ()
endfunction() endfunction()
@ -69,8 +74,6 @@ function(add_module_library name)
target_compile_options(${name} PUBLIC -fmodules-ts) target_compile_options(${name} PUBLIC -fmodules-ts)
endif () endif ()
target_compile_definitions(${name} PRIVATE FMT_MODULE)
if (FMT_USE_CMAKE_MODULES) if (FMT_USE_CMAKE_MODULES)
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
FILES ${sources}) FILES ${sources})
@ -160,7 +163,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
# Options that control generation of various targets. # Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ON) option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@ -201,8 +204,7 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif () endif ()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
include(JoinPaths) include(JoinPaths)
@ -240,7 +242,13 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif () endif ()
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
-Wnull-dereference -Wduplicated-cond) -Wduplicated-cond)
# Workaround for GCC regression
# [12/13/14/15 regression] New (since gcc 12) false positive null-dereference in vector.resize
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108860
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnull-dereference)
endif ()
endif () endif ()
set(WERROR_FLAG -Werror) set(WERROR_FLAG -Werror)
endif () endif ()
@ -289,6 +297,7 @@ function(add_headers VAR)
endfunction() endfunction()
# Define the fmt library, its includes and the needed defines. # Define the fmt library, its includes and the needed defines.
set(FMT_HEADERS)
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h) xchar.h)
@ -319,7 +328,7 @@ else ()
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler") message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
endif () endif ()
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -359,8 +368,8 @@ if (NOT MSVC)
# Unicode is always supported on compilers other than MSVC. # Unicode is always supported on compilers other than MSVC.
elseif (FMT_UNICODE) elseif (FMT_UNICODE)
# Unicode support requires compiling with /utf-8. # Unicode support requires compiling with /utf-8.
target_compile_options(fmt PUBLIC $<$<COMPILE_LANGUAGE:CXX>:/utf-8>) target_compile_options(fmt PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(fmt-header-only INTERFACE $<$<COMPILE_LANGUAGE:CXX>:/utf-8>) target_compile_options(fmt-header-only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
else () else ()
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0) target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
endif () endif ()
@ -369,7 +378,7 @@ target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
target_compile_features(fmt-header-only INTERFACE cxx_std_11) target_compile_features(fmt-header-only INTERFACE cxx_std_11)
target_include_directories(fmt-header-only target_include_directories(fmt-header-only
${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -420,7 +429,9 @@ if (FMT_INSTALL)
endif() endif()
# Install the library and headers. # Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} install(TARGETS ${INSTALL_TARGETS}
COMPONENT fmt_core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR} LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR}
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
@ -433,13 +444,15 @@ if (FMT_INSTALL)
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
# Install version, config and target files. # Install version, config and target files.
install( install(FILES ${project_config} ${version_config}
FILES ${project_config} ${version_config} DESTINATION ${FMT_CMAKE_DIR}
DESTINATION ${FMT_CMAKE_DIR}) COMPONENT fmt_core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::) NAMESPACE fmt::
COMPONENT fmt_core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT fmt_core)
endif () endif ()
function(add_doc_target) function(add_doc_target)
@ -475,7 +488,8 @@ function(add_doc_target)
include(GNUInstallDirs) include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT fmt_doc OPTIONAL)
endfunction() endfunction()
if (FMT_DOC) if (FMT_DOC)

View File

@ -1,3 +1,698 @@
# 12.1.0 - 2025-10-29
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
gcc (https://github.com/fmtlib/fmt/issues/4573).
- Made `std::variant` and `std::expected` formatters work with `format_as`
(https://github.com/fmtlib/fmt/issues/4574,
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
- Made `fmt::join<string_view>` work with C++ modules
(https://github.com/fmtlib/fmt/issues/4379,
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
- Fixed a compatibility issue with C++ modules in clang
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
- Added support for cv-qualified types to the `std::optional` formatter
(https://github.com/fmtlib/fmt/issues/4561,
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
- Added demangling support (used in exception and `std::type_info` formatters)
for libc++ and clang-cl
(https://github.com/fmtlib/fmt/issues/4542,
https://github.com/fmtlib/fmt/pull/4560,
https://github.com/fmtlib/fmt/issues/4568,
https://github.com/fmtlib/fmt/pull/4571).
Thanks @FatihBAKIR and @rohitsutreja.
- Switched to global `malloc`/`free` to enable allocator customization
(https://github.com/fmtlib/fmt/issues/4569,
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
- Fixed compilation with locales disabled in the header-only mode
(https://github.com/fmtlib/fmt/issues/4550).
- Fixed compilation with clang 21 and `-std=c++20`
(https://github.com/fmtlib/fmt/issues/4552).
- Fixed a dynamic linking issue with clang-cl
(https://github.com/fmtlib/fmt/issues/4576,
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
- Fixed a warning suppression leakage on gcc
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
- Made more internal color APIs `constexpr`
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
- Fixed compatibility with clang as a host compiler for NVCC
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
- Fixed various warnings and lint issues
(https://github.com/fmtlib/fmt/issues/4565,
https://github.com/fmtlib/fmt/pull/4572,
https://github.com/fmtlib/fmt/pull/4557).
Thanks @LiangHuDream and @teruyamato0731.
- Improved documentation
(https://github.com/fmtlib/fmt/issues/4549,
https://github.com/fmtlib/fmt/pull/4551,
https://github.com/fmtlib/fmt/issues/4566,
https://github.com/fmtlib/fmt/pull/4567,
https://github.com/fmtlib/fmt/pull/4578,).
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
# 12.0.0 - 2025-09-17
- Optimized the default floating point formatting
(https://github.com/fmtlib/fmt/issues/3675,
https://github.com/fmtlib/fmt/issues/4516). In particular, formatting a
`double` with format string compilation into a stack allocated buffer is
more than 60% faster in version 12.0 compared to 11.2 according to
[dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark):
```
Function Time (ns) Speedup
fmt11 34.471 1.00x
fmt12 21.000 1.64x
```
<img width="766" height="609" src="https://github.com/user-attachments/assets/d7d768ad-7543-468c-b0bb-449abf73b31b" />
- Added `constexpr` support to `fmt::format`. For example:
```c++
#include <fmt/compile.h>
using namespace fmt::literals;
std::string s = fmt::format(""_cf, 42);
```
now works at compile time provided that `std::string` supports `constexpr`
(https://github.com/fmtlib/fmt/issues/3403,
https://github.com/fmtlib/fmt/pull/4456). Thanks @msvetkin.
- Added `FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time.
For example:
```c++
#include <fmt/compile.h>
constexpr auto s = FMT_STATIC_FORMAT("{}", 42);
```
compiles to just
```s
__ZL1s:
.asciiz "42"
```
It can be accessed as a C string with `s.c_str()` or as a string view with
`s.str()`.
- Improved C++20 module support
(https://github.com/fmtlib/fmt/pull/4451,
https://github.com/fmtlib/fmt/pull/4459,
https://github.com/fmtlib/fmt/pull/4476,
https://github.com/fmtlib/fmt/pull/4488,
https://github.com/fmtlib/fmt/issues/4491,
https://github.com/fmtlib/fmt/pull/4495).
Thanks @arBmind, @tkhyn, @Mishura4, @anonymouspc and @autoantwort.
- Switched to using estimated display width in precision. For example:
```c++
fmt::print("|{:.4}|\n|1234|\n", "🐱🐱🐱");
```
prints
![](https://github.com/user-attachments/assets/6c4446b3-13eb-43b9-b74a-b4543540ad6a)
because `🐱` has an estimated width of 2
(https://github.com/fmtlib/fmt/issues/4272,
https://github.com/fmtlib/fmt/pull/4443,
https://github.com/fmtlib/fmt/pull/4475).
Thanks @nikhilreddydev and @localspook.
- Fix interaction between debug presentation, precision, and width for strings
(https://github.com/fmtlib/fmt/pull/4478). Thanks @localspook.
- Implemented allocator propagation on `basic_memory_buffer` move
(https://github.com/fmtlib/fmt/issues/4487,
https://github.com/fmtlib/fmt/pull/4490). Thanks @toprakmurat.
- Fixed an ambiguity between `std::reference_wrapper<T>` and `format_as`
formatters (https://github.com/fmtlib/fmt/issues/4424,
https://github.com/fmtlib/fmt/pull/4434). Thanks @jeremy-rifkin.
- Removed the following deprecated APIs:
- `has_formatter`: use `is_formattable` instead,
- `basic_format_args::parse_context_type`,
`basic_format_args::formatter_type` and similar aliases in context types,
- wide stream overload of `fmt::printf`,
- wide stream overloads of `fmt::print` that take text styles,
- `is_*char` traits,
- `fmt::localtime`.
- Deprecated wide overloads of `fmt::fprintf` and `fmt::sprintf`.
- Improved diagnostics for the incorrect usage of `fmt::ptr`
(https://github.com/fmtlib/fmt/pull/4453). Thanks @TobiSchluter.
- Made handling of ANSI escape sequences more efficient
(https://github.com/fmtlib/fmt/pull/4511,
https://github.com/fmtlib/fmt/pull/4528).
Thanks @localspook and @Anas-Hamdane.
- Fixed a buffer overflow on all emphasis flags set
(https://github.com/fmtlib/fmt/pull/4498). Thanks @dominicpoeschko.
- Fixed an integer overflow for precision close to the max `int` value.
- Fixed compatibility with WASI (https://github.com/fmtlib/fmt/issues/4496,
https://github.com/fmtlib/fmt/pull/4497). Thanks @whitequark.
- Fixed `back_insert_iterator` detection, preventing a fallback on slower path
that handles arbitrary iterators (https://github.com/fmtlib/fmt/issues/4454).
- Fixed handling of invalid glibc `FILE` buffers
(https://github.com/fmtlib/fmt/issues/4469).
- Added `wchar_t` support to the `std::byte` formatter
(https://github.com/fmtlib/fmt/issues/4479,
https://github.com/fmtlib/fmt/pull/4480). Thanks @phprus.
- Changed component prefix from `fmt-` to `fmt_` for compatibility with
NSIS/CPack on Windows, e.g. `fmt-doc` changed to `fmt_doc`
(https://github.com/fmtlib/fmt/issues/4441,
https://github.com/fmtlib/fmt/pull/4442). Thanks @n-stein.
- Added the `FMT_CUSTOM_ASSERT_FAIL` macro to simplify providing a custom
`fmt::assert_fail` implementation (https://github.com/fmtlib/fmt/pull/4505).
Thanks @HazardyKnusperkeks.
- Switched to `FMT_THROW` on reporting format errors so that it can be
overriden by users when exceptions are disabled
(https://github.com/fmtlib/fmt/pull/4521). Thanks @HazardyKnusperkeks.
- Improved master project detection and disabled install targets when using
{fmt} as a subproject by default (https://github.com/fmtlib/fmt/pull/4536).
Thanks @crueter.
- Made various code improvements
(https://github.com/fmtlib/fmt/pull/4445,
https://github.com/fmtlib/fmt/pull/4448,
https://github.com/fmtlib/fmt/pull/4473,
https://github.com/fmtlib/fmt/pull/4522).
Thanks @localspook, @tchaikov and @way4sahil.
- Added Conan instructions to the docs
(https://github.com/fmtlib/fmt/pull/4537). Thanks @uilianries.
- Removed Bazel files to avoid issues with downstream packaging
(https://github.com/fmtlib/fmt/pull/4530). Thanks @mering.
- Added more entries for generated files to `.gitignore`
(https://github.com/fmtlib/fmt/pull/4355,
https://github.com/fmtlib/fmt/pull/4512).
Thanks @dinomight and @localspook.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/pull/4447,
https://github.com/fmtlib/fmt/issues/4470,
https://github.com/fmtlib/fmt/pull/4474,
https://github.com/fmtlib/fmt/pull/4477,
https://github.com/fmtlib/fmt/pull/4471,
https://github.com/fmtlib/fmt/pull/4483,
https://github.com/fmtlib/fmt/pull/4515,
https://github.com/fmtlib/fmt/issues/4533,
https://github.com/fmtlib/fmt/pull/4534).
Thanks @dodomorandi, @localspook, @remyjette, @Tomek-Stolarczyk, @Mishura4,
@mattiasljungstrom and @FatihBAKIR.
# 11.2.0 - 2025-05-03
- Added the `s` specifier for `std::error_code`. It allows formatting an error
message as a string. For example:
```c++
#include <fmt/std.h>
int main() {
auto ec = std::make_error_code(std::errc::no_such_file_or_directory);
fmt::print("{:s}\n", ec);
}
```
prints
```
No such file or directory
```
(The actual message is platform-specific.)
- Fixed formatting of `std::chrono::local_time` and `tm`
(https://github.com/fmtlib/fmt/issues/3815,
https://github.com/fmtlib/fmt/issues/4350).
For example ([godbolt](https://www.godbolt.org/z/8o4b1PPn5)):
```c++
#include <fmt/chrono.h>
int main() {
std::chrono::zoned_time zt(
std::chrono::current_zone(),
std::chrono::system_clock::now());
fmt::print("{}", zt.get_local_time());
}
```
is now formatted consistenly across platforms.
- Added diagnostics for cases when timezone information is not available.
For example:
```c++
fmt::print("{:Z}", std::chrono::local_seconds());
```
now gives a compile-time error.
- Deprecated `fmt::localtime` in favor of `std::localtime`.
- Fixed compilation with GCC 15 and C++20 modules enabled
(https://github.com/fmtlib/fmt/pull/4347). Thanks @tkhyn.
- Fixed handling of named arguments in format specs
(https://github.com/fmtlib/fmt/issues/4360,
https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight.
- Added error reporting for duplicate named arguments
(https://github.com/fmtlib/fmt/issues/4282,
https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight.
- Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0`
(https://github.com/fmtlib/fmt/issues/4375,
https://github.com/fmtlib/fmt/issues/4394).
- Optimized `text_style` using bit packing
(https://github.com/fmtlib/fmt/pull/4363). Thanks @localspook.
- Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180,
https://github.com/fmtlib/fmt/pull/4383). Thanks @localspook.
- Fixed a flush issue in `fmt::print` when using libstdc++
(https://github.com/fmtlib/fmt/issues/4398).
- Fixed `fmt::println` usage with `FMT_ENFORCE_COMPILE_STRING` and legacy
compile-time checks (https://github.com/fmtlib/fmt/pull/4407).
Thanks @madmaxoft.
- Removed legacy header `fmt/core.h` from docs
(https://github.com/fmtlib/fmt/pull/4421,
https://github.com/fmtlib/fmt/pull/4422). Thanks @krzysztofkortas.
- Worked around limitations of `__builtin_strlen` during constant evaluation
(https://github.com/fmtlib/fmt/issues/4423,
https://github.com/fmtlib/fmt/pull/4429). Thanks @BRevzin.
- Worked around a bug in MSVC v141 (https://github.com/fmtlib/fmt/issues/4412,
https://github.com/fmtlib/fmt/pull/4413). Thanks @hirohira9119.
- Removed the `fmt_detail` namespace
(https://github.com/fmtlib/fmt/issues/4324).
- Removed specializations of `std::is_floating_point` in tests
(https://github.com/fmtlib/fmt/issues/4417).
- Fixed a CMake error when setting `CMAKE_MODULE_PATH` in the pedantic mode
(https://github.com/fmtlib/fmt/pull/4426). Thanks @rlalik.
- Updated the Bazel config (https://github.com/fmtlib/fmt/pull/4400).
Thanks @Vertexwahn.
# 11.1.4 - 2025-02-26
- Fixed ABI compatibility with earlier 11.x versions on Windows
(https://github.com/fmtlib/fmt/issues/4359).
- Improved the logic of switching between fixed and exponential format for
`float` (https://github.com/fmtlib/fmt/issues/3649).
- Moved `is_compiled_string` to the public API
(https://github.com/fmtlib/fmt/issues/4335,
https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
- Simplified implementation of `operator""_cf`
(https://github.com/fmtlib/fmt/pull/4349). Thanks @localspook.
- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329).
Thanks @localspook.
- Fixed handling of BMI paths with the Ninja generator
(https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn.
- Fixed gcc 8.3 compile errors (https://github.com/fmtlib/fmt/issues/4331,
https://github.com/fmtlib/fmt/pull/4336). Thanks @sergiud.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/pull/4356).
Thanks @dinomight.
# 11.1.3 - 2025-01-25
- Fixed compilation on GCC 9.4 (https://github.com/fmtlib/fmt/issues/4313).
- Worked around an internal compiler error when using C++20 modules with GCC
14.2 and earlier (https://github.com/fmtlib/fmt/issues/4295).
- Worked around a bug in GCC 6 (https://github.com/fmtlib/fmt/issues/4318).
- Fixed an issue caused by instantiating `formatter<const T>`
(https://github.com/fmtlib/fmt/issues/4303,
https://github.com/fmtlib/fmt/pull/4325). Thanks @timsong-cpp.
- Fixed formatting into `std::ostreambuf_iterator` when using format string
compilation (https://github.com/fmtlib/fmt/issues/4309,
https://github.com/fmtlib/fmt/pull/4312). Thanks @phprus.
- Restored a constraint on the map formatter so that it correctly reports as
unformattable when the element is (https://github.com/fmtlib/fmt/pull/4326).
Thanks @timsong-cpp.
- Reduced the size of format specs (https://github.com/fmtlib/fmt/issues/4298).
- Readded `args()` to `fmt::format_context`
(https://github.com/fmtlib/fmt/issues/4307,
https://github.com/fmtlib/fmt/pull/4310). Thanks @Erroneous1.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/issues/4314,
https://github.com/fmtlib/fmt/pull/4322). Thanks @ZehMatt.
- Fixed a pedantic mode error in the CMake config
(https://github.com/fmtlib/fmt/pull/4327). Thanks @rlalik.
# 11.1.2 - 2025-01-12
- Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4292).
- Added `wchar_t` support to the `std::bitset` formatter
(https://github.com/fmtlib/fmt/issues/4285,
https://github.com/fmtlib/fmt/pull/4286,
https://github.com/fmtlib/fmt/issues/4289,
https://github.com/fmtlib/fmt/pull/4290). Thanks @phprus.
- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via
`add_subdirectory` (https://github.com/fmtlib/fmt/issues/4283).
- Updated docs for meson (https://github.com/fmtlib/fmt/pull/4291).
Thanks @trim21.
- Fixed a compilation error in chrono on nvcc
(https://github.com/fmtlib/fmt/issues/4297,
https://github.com/fmtlib/fmt/pull/4301). Thanks @breyerml.
- Fixed various warnings
(https://github.com/fmtlib/fmt/pull/4288,
https://github.com/fmtlib/fmt/pull/4299). Thanks @GamesTrap and @edo9300.
# 11.1.1 - 2024-12-27
- Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4278).
- Defined CMake components (`core` and `doc`) to allow docs to be installed
separately (https://github.com/fmtlib/fmt/pull/4276).
Thanks @carlsmedstad.
# 11.1.0 - 2024-12-25
- Improved C++20 module support
(https://github.com/fmtlib/fmt/issues/4081,
https://github.com/fmtlib/fmt/pull/4083,
https://github.com/fmtlib/fmt/pull/4084,
https://github.com/fmtlib/fmt/pull/4152,
https://github.com/fmtlib/fmt/issues/4153,
https://github.com/fmtlib/fmt/pull/4169,
https://github.com/fmtlib/fmt/issues/4190,
https://github.com/fmtlib/fmt/issues/4234,
https://github.com/fmtlib/fmt/pull/4239).
Thanks @kamrann and @Arghnews.
- Reduced debug (unoptimized) binary code size and the number of template
instantiations when passing formatting arguments. For example, unoptimized
binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and
~60% on clang (x86-64).
GCC:
- Before: 161 instructions of which 105 are in reusable functions
([godbolt](https://www.godbolt.org/z/s9bGoo4ze)).
- After: 116 instructions of which 60 are in reusable functions
([godbolt](https://www.godbolt.org/z/r7GGGxMs6)).
Clang:
- Before: 310 instructions of which 251 are in reusable functions
([godbolt](https://www.godbolt.org/z/Ts88b7M9o)).
- After: 194 instructions of which 135 are in reusable functions
([godbolt](https://www.godbolt.org/z/vcrjP8ceW)).
- Added an experimental `fmt::writer` API that can be used for writing to
different destinations such as files or strings
(https://github.com/fmtlib/fmt/issues/2354).
For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)):
```c++
#include <fmt/os.h>
void write_text(fmt::writer w) {
w.print("The answer is {}.", 42);
}
int main() {
// Write to FILE.
write_text(stdout);
// Write to fmt::ostream.
auto f = fmt::output_file("myfile");
write_text(f);
// Write to std::string.
auto sb = fmt::string_buffer();
write_text(sb);
std::string s = sb.str();
}
```
- Added width and alignment support to the formatter of `std::error_code`.
- Made `std::expected<void, E>` formattable
(https://github.com/fmtlib/fmt/issues/4145,
https://github.com/fmtlib/fmt/pull/4148).
For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)):
```c++
fmt::print("{}", std::expected<void, int>());
```
prints
```
expected()
```
Thanks @phprus.
- Made `fmt::is_formattable<void>` SFINAE-friendly
(https://github.com/fmtlib/fmt/issues/4147).
- Added support for `_BitInt` formatting when using clang
(https://github.com/fmtlib/fmt/issues/4007,
https://github.com/fmtlib/fmt/pull/4072,
https://github.com/fmtlib/fmt/issues/4140,
https://github.com/fmtlib/fmt/issues/4173,
https://github.com/fmtlib/fmt/pull/4176).
For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)):
```c++
using int42 = _BitInt(42);
fmt::print("{}", int42(100));
```
Thanks @Arghnews.
- Added the `n` specifier for tuples and pairs
(https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc.
- Added support for tuple-like types to `fmt::join`
(https://github.com/fmtlib/fmt/issues/4226,
https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus.
- Made more types formattable at compile time
(https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH.
- Implemented a more efficient compile-time `fmt::formatted_size`
(https://github.com/fmtlib/fmt/issues/4102,
https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus.
- Fixed compile-time formatting of some string types
(https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd.
- Made compiled version of `fmt::format_to` work with
`std::back_insert_iterator<std::vector<char>>`
(https://github.com/fmtlib/fmt/issues/4206,
https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus.
- Added a formatter for `std::reference_wrapper`
(https://github.com/fmtlib/fmt/pull/4163,
https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus.
- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j`
and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH.
- Made microseconds formatted as `us` instead of `µs` if the Unicode support is
disabled (https://github.com/fmtlib/fmt/issues/4088).
- Fixed an unreleased regression in transcoding of surrogate pairs
(https://github.com/fmtlib/fmt/issues/4094,
https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus.
- Made `fmt::appender` satisfy `std::output_iterator` concept
(https://github.com/fmtlib/fmt/issues/4092,
https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus.
- Made `std::iterator_traits<fmt::appender>` standard-conforming
(https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter.
- Made it easier to reuse `fmt::formatter<std::string_view>` for types with
an implicit conversion to `std::string_view`
(https://github.com/fmtlib/fmt/issues/4036,
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
- Made it possible to disable `<filesystem>` use via `FMT_CPP_LIB_FILESYSTEM`
for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK
(https://github.com/fmtlib/fmt/issues/4257,
https://github.com/fmtlib/fmt/pull/4258,
https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus.
- Fixed compatibility with platforms that use 80-bit `long double`
(https://github.com/fmtlib/fmt/issues/4245,
https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma.
- Added support for UTF-32 code units greater than `0xFFFF` in fill
(https://github.com/fmtlib/fmt/issues/4201).
- Fixed handling of legacy encodings on Windows with GCC
(https://github.com/fmtlib/fmt/issues/4162).
- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference
(https://github.com/fmtlib/fmt/issues/4261,
https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel.
- Added `fmt::dynamic_format_arg_store::size`
(https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch.
- Removed the ability to control locale usage via an undocumented
`FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`.
- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other
similar macros.
- Improved include directory ordering to reduce the chance of including
incorrect headers when using multiple versions of {fmt}
(https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan.
- Made it possible to compile a subset of {fmt} without the C++ runtime.
- Improved documentation and README
(https://github.com/fmtlib/fmt/pull/4066,
https://github.com/fmtlib/fmt/issues/4117,
https://github.com/fmtlib/fmt/issues/4203,
https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh.
- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110,
https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado.
- Improved CI (https://github.com/fmtlib/fmt/pull/4155,
https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/issues/2708,
https://github.com/fmtlib/fmt/issues/4091,
https://github.com/fmtlib/fmt/issues/4109,
https://github.com/fmtlib/fmt/issues/4113,
https://github.com/fmtlib/fmt/issues/4125,
https://github.com/fmtlib/fmt/issues/4129,
https://github.com/fmtlib/fmt/pull/4130,
https://github.com/fmtlib/fmt/pull/4131,
https://github.com/fmtlib/fmt/pull/4132,
https://github.com/fmtlib/fmt/issues/4133,
https://github.com/fmtlib/fmt/issues/4144,
https://github.com/fmtlib/fmt/issues/4150,
https://github.com/fmtlib/fmt/issues/4158,
https://github.com/fmtlib/fmt/pull/4159,
https://github.com/fmtlib/fmt/issues/4160,
https://github.com/fmtlib/fmt/pull/4170,
https://github.com/fmtlib/fmt/issues/4177,
https://github.com/fmtlib/fmt/pull/4187,
https://github.com/fmtlib/fmt/pull/4188,
https://github.com/fmtlib/fmt/pull/4194,
https://github.com/fmtlib/fmt/pull/4200,
https://github.com/fmtlib/fmt/issues/4205,
https://github.com/fmtlib/fmt/issues/4207,
https://github.com/fmtlib/fmt/pull/4208,
https://github.com/fmtlib/fmt/pull/4210,
https://github.com/fmtlib/fmt/issues/4220,
https://github.com/fmtlib/fmt/issues/4231,
https://github.com/fmtlib/fmt/issues/4232,
https://github.com/fmtlib/fmt/pull/4233,
https://github.com/fmtlib/fmt/pull/4236,
https://github.com/fmtlib/fmt/pull/4267,
https://github.com/fmtlib/fmt/pull/4271).
Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega,
@c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao.
# 11.0.2 - 2024-07-20
- Fixed compatibility with non-POSIX systems
(https://github.com/fmtlib/fmt/issues/4054,
https://github.com/fmtlib/fmt/issues/4060).
- Fixed performance regressions when using `std::back_insert_iterator` with
`fmt::format_to` (https://github.com/fmtlib/fmt/issues/4070).
- Fixed handling of `std::generator` and move-only iterators
(https://github.com/fmtlib/fmt/issues/4053,
https://github.com/fmtlib/fmt/pull/4057). Thanks @Arghnews.
- Made `formatter<std::string_view>::parse` work with types convertible to
`std::string_view` (https://github.com/fmtlib/fmt/issues/4036,
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
- Made `volatile void*` formattable
(https://github.com/fmtlib/fmt/issues/4049,
https://github.com/fmtlib/fmt/pull/4056). Thanks @Arghnews.
- Made `Glib::ustring` not be confused with `std::string`
(https://github.com/fmtlib/fmt/issues/4052).
- Made `fmt::context` iterator compatible with STL algorithms that rely on
iterator category (https://github.com/fmtlib/fmt/issues/4079).
# 11.0.1 - 2024-07-05 # 11.0.1 - 2024-07-05
- Fixed version number in the inline namespace - Fixed version number in the inline namespace
@ -13,6 +708,9 @@
(https://github.com/fmtlib/fmt/pull/4034, (https://github.com/fmtlib/fmt/pull/4034,
https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus. https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus.
- Fixed ADL issues in `fmt::printf` when using C++20
(https://github.com/fmtlib/fmt/pull/4042). Thanks @toge.
- Removed a redundant check in the formatter for `std::expected` - Removed a redundant check in the formatter for `std::expected`
(https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus. (https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus.
@ -238,6 +936,9 @@
- Fixed handling of negative ids in `fmt::basic_format_args::get` - Fixed handling of negative ids in `fmt::basic_format_args::get`
(https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota. (https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota.
- Fixed handling of a buffer boundary on flush
(https://github.com/fmtlib/fmt/issues/4229).
- Improved named argument validation - Improved named argument validation
(https://github.com/fmtlib/fmt/issues/3817). (https://github.com/fmtlib/fmt/issues/3817).

View File

@ -4,14 +4,15 @@
[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos) [![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows) [![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1) [![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8880/badge)](https://www.bestpractices.dev/projects/8880)
[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) [![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)
**{fmt}** is an open-source formatting library providing a fast and safe **{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams. alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to one of the funds If you like this project, please consider donating to one of the funds
that help victims of the war in Ukraine: <https://www.stopputin.net/>. that help victims of the war in Ukraine: <https://u24.gov.ua/>.
[Documentation](https://fmt.dev) [Documentation](https://fmt.dev)
@ -24,12 +25,12 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
# Features # Features
- Simple [format API](https://fmt.dev/latest/api.html) with positional - Simple [format API](https://fmt.dev/latest/api/) with positional
arguments for localization arguments for localization
- Implementation of [C++20 - Implementation of [C++20
std::format](https://en.cppreference.com/w/cpp/utility/format) and std::format](https://en.cppreference.com/w/cpp/utility/format) and
[C++23 std::print](https://en.cppreference.com/w/cpp/io/print) [C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
- [Format string syntax](https://fmt.dev/latest/syntax.html) similar - [Format string syntax](https://fmt.dev/latest/syntax/) similar
to Python\'s to Python\'s
[format](https://docs.python.org/3/library/stdtypes.html#str.format) [format](https://docs.python.org/3/library/stdtypes.html#str.format)
- Fast IEEE 754 floating-point formatter with correct rounding, - Fast IEEE 754 floating-point formatter with correct rounding,
@ -37,17 +38,17 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
[Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
- Portable Unicode support - Portable Unicode support
- Safe [printf - Safe [printf
implementation](https://fmt.dev/latest/api.html#printf-formatting) implementation](https://fmt.dev/latest/api/#printf-formatting)
including the POSIX extension for positional arguments including the POSIX extension for positional arguments
- Extensibility: [support for user-defined - Extensibility: [support for user-defined
types](https://fmt.dev/latest/api.html#formatting-user-defined-types) types](https://fmt.dev/latest/api/#formatting-user-defined-types)
- High performance: faster than common standard library - High performance: faster than common standard library
implementations of `(s)printf`, iostreams, `to_string` and implementations of `(s)printf`, iostreams, `to_string` and
`to_chars`, see [Speed tests](#speed-tests) and [Converting a `to_chars`, see [Speed tests](#speed-tests) and [Converting a
hundred million integers to strings per hundred million integers to strings per
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html) second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
- Small code size both in terms of source code with the minimum - Small code size both in terms of source code with the minimum
configuration consisting of just three files, `core.h`, `format.h` configuration consisting of just three files, `base.h`, `format.h`
and `format-inl.h`, and compiled code; see [Compile time and code and `format-inl.h`, and compiled code; see [Compile time and code
bloat](#compile-time-and-code-bloat) bloat](#compile-time-and-code-bloat)
- Reliability: the library has an extensive set of - Reliability: the library has an extensive set of
@ -58,8 +59,8 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
buffer overflow errors buffer overflow errors
- Ease of use: small self-contained code base, no external - Ease of use: small self-contained code base, no external
dependencies, permissive MIT dependencies, permissive MIT
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst) [license](https://github.com/fmtlib/fmt/blob/master/LICENSE)
- [Portability](https://fmt.dev/latest/index.html#portability) with - [Portability](https://fmt.dev/latest/#portability) with
consistent output across platforms and support for older compilers consistent output across platforms and support for older compilers
- Clean warning-free codebase even on high warning levels such as - Clean warning-free codebase even on high warning levels such as
`-Wall -Wextra -pedantic` `-Wall -Wextra -pedantic`
@ -74,7 +75,7 @@ See the [documentation](https://fmt.dev) for more details.
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh)) **Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
``` c++ ``` c++
#include <fmt/core.h> #include <fmt/base.h>
int main() { int main() {
fmt::print("Hello, world!\n"); fmt::print("Hello, world!\n");
@ -149,8 +150,8 @@ int main() {
} }
``` ```
This can be [5 to 9 times faster than This can be [up to 9 times faster than `fprintf`](
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles** **Print with colors and text styles**
@ -177,17 +178,17 @@ Output on a modern terminal with Unicode support:
| Library | Method | Run Time, s | | Library | Method | Run Time, s |
|-------------------|---------------|-------------| |-------------------|---------------|-------------|
| libc | printf | 0.91 | | libc | printf | 0.66 |
| libc++ | std::ostream | 2.49 | | libc++ | std::ostream | 1.63 |
| {fmt} 9.1 | fmt::print | 0.74 | | {fmt} 12.1 | fmt::print | 0.44 |
| Boost Format 1.80 | boost::format | 6.26 | | Boost Format 1.88 | boost::format | 3.89 |
| Folly Format | folly::format | 1.87 | | Folly Format | folly::format | 1.28 |
{fmt} is the fastest of the benchmarked methods, \~20% faster than {fmt} is the fastest of the benchmarked methods, \~50% faster than
`printf`. `printf`.
The above results were generated by building `tinyformat_test.cpp` on The above results were generated by building `tinyformat_test.cpp` on
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
taking the best of three runs. In the test, the format string taking the best of three runs. In the test, the format string
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000 `"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
times with output sent to `/dev/null`; for further details refer to the times with output sent to `/dev/null`; for further details refer to the
@ -215,26 +216,26 @@ in the following tables.
**Optimized build (-O3)** **Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | | Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------| |-----------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 | | printf | 1.6 | 54 | 50 |
| IOStreams | 25.9 | 98 | 84 | | IOStreams | 28.4 | 98 | 84 |
| fmt 83652df | 4.8 | 54 | 50 | | {fmt} `1122268` | 5.0 | 54 | 50 |
| tinyformat | 29.1 | 161 | 136 | | tinyformat | 32.6 | 164 | 136 |
| Boost Format | 55.0 | 530 | 317 | | Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call {fmt} is fast to compile and is comparable to `printf` in terms of per-call
binary size (within a rounding error on this system). binary size (within a rounding error on this system).
**Non-optimized build** **Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | | Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------| |-----------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 | | printf | 1.4 | 54 | 50 |
| IOStreams | 23.4 | 92 | 68 | | IOStreams | 27.0 | 88 | 68 |
| {fmt} 83652df | 4.4 | 89 | 85 | | {fmt} `1122268` | 4.7 | 87 | 84 |
| tinyformat | 24.5 | 204 | 161 | | tinyformat | 28.1 | 185 | 145 |
| Boost Format | 36.4 | 831 | 462 | | Boost Format | 38.9 | 678 | 381 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries `libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a to compare formatting function overhead only. Boost Format is a
@ -243,7 +244,7 @@ header-only library so it doesn\'t provide any linkage options.
## Running the tests ## Running the tests
Please refer to [Building the Please refer to [Building the
library](https://fmt.dev/latest/usage.html#building-the-library) for library](https://fmt.dev/latest/get-started/#building-from-source) for
instructions on how to build the library and run the unit tests. instructions on how to build the library and run the unit tests.
Benchmarks reside in a separate repository, Benchmarks reside in a separate repository,
@ -291,13 +292,14 @@ converts to `std::print`.)
- [ccache](https://ccache.dev/): a compiler cache - [ccache](https://ccache.dev/): a compiler cache
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an - [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
analytical database management system analytical database management system
- [ContextVision](https://www.contextvision.com/): medical imaging software
- [Contour](https://github.com/contour-terminal/contour/): a modern - [Contour](https://github.com/contour-terminal/contour/): a modern
terminal emulator terminal emulator
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous - [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
underwater vehicle underwater vehicle
- [Drake](https://drake.mit.edu/): a planning, control, and analysis - [Drake](https://drake.mit.edu/): a planning, control, and analysis
toolbox for nonlinear dynamical systems (MIT) toolbox for nonlinear dynamical systems (MIT)
- [Envoy](https://lyft.github.io/envoy/): C++ L7 proxy and - [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and
communication bus (Lyft) communication bus (Lyft)
- [FiveM](https://fivem.net/): a modification framework for GTA V - [FiveM](https://fivem.net/): a modification framework for GTA V
- [fmtlog](https://github.com/MengRao/fmtlog): a performant - [fmtlog](https://github.com/MengRao/fmtlog): a performant
@ -456,7 +458,7 @@ second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).
# Documentation License # Documentation License
The [Format String Syntax](https://fmt.dev/latest/syntax.html) section The [Format String Syntax](https://fmt.dev/latest/syntax/) section
in the documentation is based on the one from Python [string module in the documentation is based on the one from Python [string module
documentation](https://docs.python.org/3/library/string.html#module-string). documentation](https://docs.python.org/3/library/string.html#module-string).
For this reason, the documentation is distributed under the Python For this reason, the documentation is distributed under the Python

View File

@ -674,7 +674,7 @@
https://github.com/fmtlib/fmt/issues/1747, https://github.com/fmtlib/fmt/issues/1747,
https://github.com/fmtlib/fmt/pull/1750). https://github.com/fmtlib/fmt/pull/1750).
Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10, Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10,
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink, @daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime,
@tohammer and @0x8000-0000. @tohammer and @0x8000-0000.
# 6.2.1 - 2020-05-09 # 6.2.1 - 2020-05-09

View File

@ -14,7 +14,7 @@ The {fmt} library API consists of the following components:
- [`fmt/os.h`](#os-api): system APIs - [`fmt/os.h`](#os-api): system APIs
- [`fmt/ostream.h`](#ostream-api): `std::ostream` support - [`fmt/ostream.h`](#ostream-api): `std::ostream` support
- [`fmt/args.h`](#args-api): dynamic argument lists - [`fmt/args.h`](#args-api): dynamic argument lists
- [`fmt/printf.h`](#printf-api): `printf` formatting - [`fmt/printf.h`](#printf-api): safe `printf`
- [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support - [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support
All functions and types provided by the library reside in namespace `fmt` All functions and types provided by the library reside in namespace `fmt`
@ -79,6 +79,8 @@ time formatting and [`fmt/std.h`](#std-api) for other standard library types.
There are two ways to make a user-defined type formattable: providing a There are two ways to make a user-defined type formattable: providing a
`format_as` function or specializing the `formatter` struct template. `format_as` function or specializing the `formatter` struct template.
Formatting of non-void pointer types is intentionally disallowed and they
cannot be made formattable via either extension API.
Use `format_as` if you want to make your type formattable as some other Use `format_as` if you want to make your type formattable as some other
type with the same format specifiers. The `format_as` function should type with the same format specifiers. The `format_as` function should
@ -220,7 +222,7 @@ You can also write a formatter for a hierarchy of classes:
```c++ ```c++
// demo.h: // demo.h:
#include <type_traits> #include <type_traits>
#include <fmt/core.h> #include <fmt/format.h>
struct A { struct A {
virtual ~A() {} virtual ~A() {}
@ -269,18 +271,16 @@ that support C++20 `consteval`. On older compilers you can use the
Unused arguments are allowed as in Python's `str.format` and ordinary functions. Unused arguments are allowed as in Python's `str.format` and ordinary functions.
::: basic_format_string See [Type Erasure](#type-erasure) for an example of how to enable compile-time
checks in your own functions with `fmt::format_string` while avoiding template
bloat.
::: fstring
::: format_string ::: format_string
::: runtime(string_view) ::: runtime(string_view)
### Named Arguments
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Type Erasure ### Type Erasure
You can create your own formatting function with compile-time checks and You can create your own formatting function with compile-time checks and
@ -317,6 +317,12 @@ parameterized version.
::: basic_format_arg ::: basic_format_arg
### Named Arguments
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Compatibility ### Compatibility
::: basic_string_view ::: basic_string_view
@ -375,18 +381,17 @@ allocator:
using custom_string = using custom_string =
std::basic_string<char, std::char_traits<char>, custom_allocator>; std::basic_string<char, std::char_traits<char>, custom_allocator>;
custom_string vformat(custom_allocator alloc, fmt::string_view format_str, auto vformat(custom_allocator alloc, fmt::string_view fmt,
fmt::format_args args) { fmt::format_args args) -> custom_string {
auto buf = custom_memory_buffer(alloc); auto buf = custom_memory_buffer(alloc);
fmt::vformat_to(std::back_inserter(buf), format_str, args); fmt::vformat_to(std::back_inserter(buf), fmt, args);
return custom_string(buf.data(), buf.size(), alloc); return custom_string(buf.data(), buf.size(), alloc);
} }
template <typename ...Args> template <typename ...Args>
inline custom_string format(custom_allocator alloc, auto format(custom_allocator alloc, fmt::string_view fmt,
fmt::string_view format_str, const Args& ... args) -> custom_string {
const Args& ... args) { return vformat(alloc, fmt, fmt::make_format_args(args...));
return vformat(alloc, format_str, fmt::make_format_args(args...));
} }
The allocator will be used for the output container only. Formatting The allocator will be used for the output container only. Formatting
@ -400,7 +405,7 @@ All formatting is locale-independent by default. Use the `'L'` format
specifier to insert the appropriate number separator characters from the specifier to insert the appropriate number separator characters from the
locale: locale:
#include <fmt/core.h> #include <fmt/format.h>
#include <locale> #include <locale>
std::locale::global(std::locale("en_US.UTF-8")); std::locale::global(std::locale("en_US.UTF-8"));
@ -410,11 +415,11 @@ locale:
that take `std::locale` as a parameter. The locale type is a template that take `std::locale` as a parameter. The locale type is a template
parameter to avoid the expensive `<locale>` include. parameter to avoid the expensive `<locale>` include.
::: format(const Locale&, format_string<T...>, T&&...) ::: format(locale_ref, format_string<T...>, T&&...)
::: format_to(OutputIt, const Locale&, format_string<T...>, T&&...) ::: format_to(OutputIt, locale_ref, format_string<T...>, T&&...)
::: formatted_size(const Locale&, format_string<T...>, T&&...) ::: formatted_size(locale_ref, format_string<T...>, T&&...)
<a id="legacy-checks"></a> <a id="legacy-checks"></a>
### Legacy Compile-Time Checks ### Legacy Compile-Time Checks
@ -470,9 +475,9 @@ chrono-format-specifications).
#include <fmt/chrono.h> #include <fmt/chrono.h>
int main() { int main() {
std::time_t t = std::time(nullptr); auto now = std::chrono::system_clock::now();
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); fmt::print("The date is {:%Y-%m-%d}.\n", now);
// Output: The date is 2020-11-07. // Output: The date is 2020-11-07.
// (with 2020-11-07 replaced by the current date) // (with 2020-11-07 replaced by the current date)
@ -485,8 +490,6 @@ chrono-format-specifications).
// Output: strftime-like format: 03:15:30 // Output: strftime-like format: 03:15:30
} }
::: localtime(std::time_t)
::: gmtime(std::time_t) ::: gmtime(std::time_t)
<a id="std-api"></a> <a id="std-api"></a>
@ -498,10 +501,13 @@ chrono-format-specifications).
- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) - [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) - [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code) - [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception)
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path) - [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
- [`std::monostate`](https://en.cppreference.com/w/cpp/utility/variant/monostate) - [`std::monostate`](
https://en.cppreference.com/w/cpp/utility/variant/monostate)
- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) - [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional)
- [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location) - [`std::source_location`](
https://en.cppreference.com/w/cpp/utility/source_location)
- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id) - [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id)
- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant) - [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant)
@ -509,7 +515,7 @@ chrono-format-specifications).
::: ptr(const std::shared_ptr<T>&) ::: ptr(const std::shared_ptr<T>&)
### Formatting Variants ### Variants
A `std::variant` is only formattable if every variant alternative is A `std::variant` is only formattable if every variant alternative is
formattable, and requires the `__cpp_lib_variant` [library formattable, and requires the `__cpp_lib_variant` [library
@ -525,39 +531,87 @@ feature](https://en.cppreference.com/w/cpp/feature_test).
fmt::print("{}", std::variant<std::monostate, char>()); fmt::print("{}", std::variant<std::monostate, char>());
// Output: variant(monostate) // Output: variant(monostate)
<a id="compile-api"></a> ## Bit-Fields and Packed Structs
## Format String Compilation
`fmt/compile.h` provides format string compilation enabled via the To format a bit-field or a field of a struct with `__attribute__((packed))`
`FMT_COMPILE` macro or the `_cf` user-defined literal defined in applied to it, you need to convert it to the underlying or compatible type via
namespace `fmt::literals`. Format strings marked with `FMT_COMPILE` a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)):
or `_cf` are parsed, checked and converted into efficient formatting
code at compile-time. This supports arguments of built-in and string ```c++
types as well as user-defined types with `format` functions taking struct smol {
int bit : 1;
};
auto s = smol();
fmt::print("{}", +s.bit);
```
This is a known limitation of "perfect" forwarding in C++.
<a id="compile-api"></a>
## Compile-Time Support
`fmt/compile.h` provides format string compilation and compile-time
(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf`
user-defined literal defined in namespace `fmt::literals`. Format strings
marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into
efficient formatting code at compile-time. This supports arguments of built-in
and string types as well as user-defined types with `format` methods taking
the format context type as a template parameter in their `formatter` the format context type as a template parameter in their `formatter`
specializations. For example: specializations. For example ([run](https://www.godbolt.org/z/3c13erEoq)):
struct point {
double x;
double y;
};
template <> struct fmt::formatter<point> { template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx); constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext> template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const; auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
}; };
using namespace fmt::literals;
std::string s = fmt::format("{}"_cf, point(4, 2));
Format string compilation can generate more binary code compared to the Format string compilation can generate more binary code compared to the
default API and is only recommended in places where formatting is a default API and is only recommended in places where formatting is a
performance bottleneck. performance bottleneck.
::: FMT_COMPILE The same APIs support formatting at compile time e.g. in `constexpr`
and `consteval` functions. Additionally there is an experimental
`FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time. Compile-time formatting works with built-in
and user-defined formatters that have `constexpr` `format` methods.
Example:
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
constexpr auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
};
constexpr auto s = FMT_STATIC_FORMAT("{}", point(4, 2));
const char* cstr = s.c_str(); // Points the static string "(4, 2)".
::: operator""_cf ::: operator""_cf
::: FMT_COMPILE
::: FMT_STATIC_FORMAT
<a id="color-api"></a> <a id="color-api"></a>
## Terminal Colors and Text Styles ## Terminal Colors and Text Styles
`fmt/color.h` provides support for terminal color and text style output. `fmt/color.h` provides support for terminal color and text style output.
::: print(const text_style&, format_string<T...>, T&&...) ::: print(text_style, format_string<T...>, T&&...)
::: fg(detail::color_type) ::: fg(detail::color_type)
@ -570,6 +624,8 @@ performance bottleneck.
::: ostream ::: ostream
::: output_file(cstring_view, T...)
::: windows_error ::: windows_error
<a id="ostream-api"></a> <a id="ostream-api"></a>
@ -609,7 +665,7 @@ that can be used to construct format argument lists dynamically.
::: dynamic_format_arg_store ::: dynamic_format_arg_store
<a id="printf-api"></a> <a id="printf-api"></a>
## `printf` Formatting ## Safe `printf`
The header `fmt/printf.h` provides `printf`-like formatting The header `fmt/printf.h` provides `printf`-like formatting
functionality. The following functions use [printf format string functionality. The following functions use [printf format string
@ -620,9 +676,9 @@ if an argument type doesn't match its format specification.
::: printf(string_view, const T&...) ::: printf(string_view, const T&...)
::: fprintf(std::FILE*, const S&, const T&...) ::: fprintf(std::FILE*, string_view, const T&...)
::: sprintf(const S&, const T&...) ::: sprintf(string_view, const T&...)
<a id="xchar-api"></a> <a id="xchar-api"></a>
## Wide Strings ## Wide Strings
@ -630,8 +686,6 @@ if an argument type doesn't match its format specification.
The optional header `fmt/xchar.h` provides support for `wchar_t` and The optional header `fmt/xchar.h` provides support for `wchar_t` and
exotic character types. exotic character types.
::: is_char
::: wstring_view ::: wstring_view
::: wformat_context ::: wformat_context
@ -646,5 +700,63 @@ following differences:
- Names are defined in the `fmt` namespace instead of `std` to avoid - Names are defined in the `fmt` namespace instead of `std` to avoid
collisions with standard library implementations. collisions with standard library implementations.
- Width calculation doesn't use grapheme clusterization. The latter has - Width calculation doesn't use grapheme clusterization. The latter has
been implemented in a separate branch but hasn't been integrated yet. been implemented in a separate branch but hasn't been integrated yet.
- The default floating-point representation in {fmt} uses the smallest
precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters
(ignoring redundant digits and sign in exponent) and may produce more
decimal digits than necessary.
## Configuration Options
{fmt} provides configuration via CMake options and preprocessor macros to
enable or disable features and to optimize for binary size. For example, you
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
configuring CMake.
### CMake Options
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
Windows/MSVC. Unicode support is always enabled on other platforms.
### Macros
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
alternative to using the `fmt::fmt-header-only` CMake target.
Default: not defined.
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
Default: `1` (`0` if compiled with `-fno-exceptions`).
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
custom `fmt::assert_fail` function which is called on assertion failures and,
if exceptions are disabled, on runtime errors. Default: `0`.
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
arithmetic and string types other than `int`. This reduces library size at
the cost of per-call overhead. Default: `1`.
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
- `0` - off (default)
- `1` - disables locale support and applies some optimizations
- `2` - disables some Unicode features, named arguments and applies more
aggressive optimizations
### Binary Size Optimization
To minimize the binary footprint of {fmt} as much as possible at the cost of
some features, you can use the following configuration:
- CMake options:
- `FMT_OS=OFF`
- Macros:
- `FMT_BUILTIN_TYPES=0`
- `FMT_OPTIMIZE_SIZE=2`

View File

@ -20,6 +20,7 @@
margin-left: 1em; margin-left: 1em;
} }
code,
pre > code.decl { pre > code.decl {
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@ -78,6 +78,17 @@ community contributors. If the version is out of date, please [create an
issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg
repository. --> repository. -->
### Conan
You can download and install {fmt} using the [Conan](https://conan.io/) package manager:
conan install -r conancenter --requires="fmt/[*]" --build=missing
<!-- The {fmt} package in Conan Center is maintained by
[ConanCenterIndex](https://github.com/conan-io/conan-center-index) community.
If the version is out of date or the package does not work,
please create an issue or pull request on the Conan Center Index repository. -->
## Building from Source ## Building from Source
CMake works by generating native makefiles or project files that can be CMake works by generating native makefiles or project files that can be
@ -202,7 +213,7 @@ For a static build, use the following subproject definition:
For the header-only version, use: For the header-only version, use:
fmt = subproject('fmt') fmt = subproject('fmt', default_options: ['header-only=true'])
fmt_dep = fmt.get_variable('fmt_header_only_dep') fmt_dep = fmt.get_variable('fmt_header_only_dep')
### Android NDK ### Android NDK

View File

@ -76,7 +76,7 @@ hide:
<p> <p>
The default is <b>locale-independent</b>, but you can opt into localized The default is <b>locale-independent</b>, but you can opt into localized
formatting and {fmt} makes it work with Unicode, addressing issues in the formatting and {fmt} makes it work with Unicode, addressing issues in the
standard libary. standard library.
</p> </p>
</div> </div>
@ -122,8 +122,8 @@ hide:
</p> </p>
<p> <p>
The library is highly portable and requires only a minimal <b>subset of The library is highly portable and requires only a minimal <b>subset of
C++11</b> features which are available in GCC 4.8, Clang 3.4, MSVC 19.0 C++11</b> features which are available in GCC 4.9, Clang 3.6, MSVC 19.10
(2015) and later. Newer compiler and standard library features are used (2017) and later. Newer compiler and standard library features are used
if available, and enable additional functionality. if available, and enable additional functionality.
</p> </p>
<p> <p>

View File

@ -251,7 +251,7 @@ The available integer presentation types are:
<td><code>'b'</code></td> <td><code>'b'</code></td>
<td> <td>
Binary format. Outputs the number in base 2. Using the <code>'#'</code> Binary format. Outputs the number in base 2. Using the <code>'#'</code>
option with this type adds the prefix <code>"0b"</code> to the output value. option with this type adds the prefix <code>"0b"</code> to the output value.
</td> </td>
</tr> </tr>
<tr> <tr>
@ -589,8 +589,7 @@ The available presentation types (*chrono_type*) are:
represented with seconds, then the format is a decimal floating-point number represented with seconds, then the format is a decimal floating-point number
with a fixed format and a precision matching that of the precision of the with a fixed format and a precision matching that of the precision of the
input (or to a microseconds precision if the conversion to floating-point input (or to a microseconds precision if the conversion to floating-point
decimal seconds cannot be made within 18 fractional digits). The character decimal seconds cannot be made within 18 fractional digits). The modified
for the decimal point is localized according to the locale. The modified
command <code>%OS</code> produces the locale's alternative representation. command <code>%OS</code> produces the locale's alternative representation.
</td> </td>
</tr> </tr>
@ -707,19 +706,19 @@ The available padding modifiers (*padding_modifier*) are:
| Type | Meaning | | Type | Meaning |
|-------|-----------------------------------------| |-------|-----------------------------------------|
| `'-'` | Pad a numeric result with spaces. | | `'_'` | Pad a numeric result with spaces. |
| `'_'` | Do not pad a numeric result string. | | `'-'` | Do not pad a numeric result string. |
| `'0'` | Pad a numeric result string with zeros. | | `'0'` | Pad a numeric result string with zeros. |
These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`, These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
`'V'` and `'W'` presentation types. `'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types.
## Range Format Specifications ## Range Format Specifications
Format specifications for range types have the following syntax: Format specifications for range types have the following syntax:
<pre><code class="language-json" <pre><code class="language-json"
>range_format_spec ::= ["n"][range_type][range_underlying_spec]</code> >range_format_spec ::= ["n"][range_type][":" range_underlying_spec]</code>
</pre> </pre>
The `'n'` option formats the range without the opening and closing brackets. The `'n'` option formats the range without the opening and closing brackets.
@ -762,14 +761,16 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [h, e, l, l, o] // Output: [h, e, l, l, o]
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'}); fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [104, 101, 108, 108, 111] // Output: [104, 101, 108, 108, 111]
fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e});
// Output: 3.141593, 2.718282
``` ```
## Format Examples ## Format Examples
This section contains examples of the format syntax and comparison with This section contains examples of the format syntax and comparison with
the printf formatting. the `printf` formatting.
In most of the cases the syntax is similar to the printf formatting, In most of the cases the syntax is similar to the `printf` formatting,
with the addition of the `{}` and with `:` used instead of `%`. For with the addition of the `{}` and with `:` used instead of `%`. For
example, `"%03.2f"` can be translated to `"{:03.2f}"`. example, `"%03.2f"` can be translated to `"{:03.2f}"`.

View File

@ -17,7 +17,6 @@
#include "format.h" // std_string_view #include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {}; template <typename T> struct is_reference_wrapper : std::false_type {};
@ -72,19 +71,13 @@ class dynamic_arg_list {
* It can be implicitly converted into `fmt::basic_format_args` for passing * It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`. * into type-erased formatting functions such as `fmt::vformat`.
*/ */
template <typename Context> FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private: private:
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
template <typename T> struct need_copy { template <typename T> struct need_copy {
static constexpr detail::type mapped_type = static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value; detail::mapped_type_constant<T, char_type>::value;
enum { enum {
value = !(detail::is_reference_wrapper<T>::value || value = !(detail::is_reference_wrapper<T>::value ||
@ -97,7 +90,7 @@ class dynamic_format_arg_store
}; };
template <typename T> template <typename T>
using stored_type = conditional_t< using stored_t = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value && std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value, !detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>; std::basic_string<char_type>, T>;
@ -112,41 +105,37 @@ class dynamic_format_arg_store
friend class basic_format_args<Context>; friend class basic_format_args<Context>;
auto get_types() const -> unsigned long long {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
auto data() const -> const basic_format_arg<Context>* { auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1; return named_info_.empty() ? data_.data() : data_.data() + 1;
} }
template <typename T> void emplace_arg(const T& arg) { template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg)); data_.emplace_back(arg);
} }
template <typename T> template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) { void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) { if (named_info_.empty())
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr}; data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
data_.insert(data_.begin(), {zero_ptr, 0}); data_.emplace_back(detail::unwrap(arg.value));
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) { auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back(); data->pop_back();
}; };
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)> std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one}; guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)}); named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; data_[0] = {named_info_.data(), named_info_.size()};
guard.release(); guard.release();
} }
public: public:
constexpr dynamic_format_arg_store() = default; constexpr dynamic_format_arg_store() = default;
operator basic_format_args<Context>() const {
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
!named_info_.empty());
}
/** /**
* Adds an argument into the dynamic store for later passing to a formatting * Adds an argument into the dynamic store for later passing to a formatting
* function. * function.
@ -164,7 +153,7 @@ class dynamic_format_arg_store
*/ */
template <typename T> void push_back(const T& arg) { template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value)) if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg)); emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
else else
emplace_arg(detail::unwrap(arg)); emplace_arg(detail::unwrap(arg));
} }
@ -200,7 +189,7 @@ class dynamic_format_arg_store
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str(); dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) { if (detail::const_check(need_copy<T>::value)) {
emplace_arg( emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value))); fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
} else { } else {
emplace_arg(fmt::arg(arg_name, arg.value)); emplace_arg(fmt::arg(arg_name, arg.value));
} }
@ -210,17 +199,20 @@ class dynamic_format_arg_store
void clear() { void clear() {
data_.clear(); data_.clear();
named_info_.clear(); named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list(); dynamic_args_ = {};
} }
/// Reserves space to store at least `new_cap` arguments including /// Reserves space to store at least `new_cap` arguments including
/// `new_cap_named` named arguments. /// `new_cap_named` named arguments.
void reserve(size_t new_cap, size_t new_cap_named) { void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named, FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments"); "set of arguments includes set of named arguments");
data_.reserve(new_cap); data_.reserve(new_cap);
named_info_.reserve(new_cap_named); named_info_.reserve(new_cap_named);
} }
/// Returns the number of elements in the store.
auto size() const noexcept -> size_t { return data_.size(); }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -155,7 +155,7 @@ enum class color : uint32_t {
white_smoke = 0xF5F5F5, // rgb(245,245,245) white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0) yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50) yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color }; // enum class color
enum class terminal_color : uint8_t { enum class terminal_color : uint8_t {
black = 30, black = 30,
@ -190,11 +190,11 @@ enum class emphasis : uint8_t {
// rgb is a struct for red, green and blue colors. // rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip. // Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb { struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} constexpr rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex) constexpr rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex) constexpr rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF), : r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {} b(uint32_t(hex) & 0xFF) {}
@ -205,97 +205,135 @@ struct rgb {
namespace detail { namespace detail {
// color is a struct of either a rgb color or a terminal color. // A bit-packed variant of an RGB color, a terminal color, or unset color.
// see text_style for the bit-packing scheme.
struct color_type { struct color_type {
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} constexpr color_type() noexcept = default;
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { constexpr color_type(color rgb_color) noexcept
value.rgb_color = static_cast<uint32_t>(rgb_color); : value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
constexpr color_type(rgb rgb_color) noexcept
: color_type(static_cast<color>(
(static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
constexpr color_type(terminal_color term_color) noexcept
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
constexpr auto is_terminal_color() const noexcept -> bool {
return (value_ & (1 << 25)) != 0;
} }
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | constexpr auto value() const noexcept -> uint32_t {
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; return value_ & 0xFFFFFF;
} }
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
: is_rgb(), value{} { constexpr color_type(uint32_t value) noexcept : value_(value) {}
value.term_color = static_cast<uint8_t>(term_color);
} uint32_t value_ = 0;
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
}; };
} // namespace detail } // namespace detail
/// A text style consisting of foreground and background colors and emphasis. /// A text style consisting of foreground and background colors and emphasis.
class text_style { class text_style {
// The information is packed as follows:
// ┌──┐
// │ 0│─┐
// │..│ ├── foreground color value
// │23│─┘
// ├──┤
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
// ├──┤
// │26│──── overflow bit, always zero (see below)
// ├──┤
// │27│─┐
// │..│ │
// │50│ │
// ├──┤ │
// │51│ ├── background color (same format as the foreground color)
// │52│ │
// ├──┤ │
// │53│─┘
// ├──┤
// │54│─┐
// │..│ ├── emphases
// │61│─┘
// ├──┤
// │62│─┬── unused
// │63│─┘
// └──┘
// The overflow bits are there to make operator|= efficient.
// When ORing, we must throw if, for either the foreground or background,
// one style specifies a terminal color and the other specifies any color
// (terminal or RGB); in other words, if one discriminator is 11 and the
// other is 11 or 01.
//
// We do that check by adding the styles. Consider what adding does to each
// possible pair of discriminators:
// 00 + 00 = 000
// 01 + 00 = 001
// 11 + 00 = 011
// 01 + 01 = 010
// 11 + 01 = 100 (!!)
// 11 + 11 = 110 (!!)
// In the last two cases, the ones we want to catch, the third bit——the
// overflow bit——is set. Bingo.
//
// We must take into account the possible carry bit from the bits
// before the discriminator. The only potentially problematic case is
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
// bit is impossible in that case, because 00 (unset color) means the
// 24 bits that precede the discriminator are all zero.
//
// This test can be applied to both colors simultaneously.
public: public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), set_background_color(), ems(em) {} : style_(static_cast<uint64_t>(em) << 54) {}
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
if (!set_foreground_color) { if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
set_foreground_color = rhs.set_foreground_color; report_error("can't OR a terminal color");
foreground_color = rhs.foreground_color; style_ |= rhs.style_;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
report_error("can't OR a terminal color");
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
report_error("can't OR a terminal color");
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this; return *this;
} }
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
-> text_style { -> text_style {
return lhs |= rhs; return lhs |= rhs;
} }
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
return style_ == rhs.style_;
}
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
return !(*this == rhs);
}
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return set_foreground_color; return (style_ & (1 << 24)) != 0;
} }
FMT_CONSTEXPR auto has_background() const noexcept -> bool { FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return set_background_color; return (style_ & (1ULL << 51)) != 0;
} }
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return static_cast<uint8_t>(ems) != 0; return (style_ >> 54) != 0;
} }
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style"); FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color; return style_ & 0x3FFFFFF;
} }
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style"); FMT_ASSERT(has_background(), "no background specified for this style");
return background_color; return (style_ >> 27) & 0x3FFFFFF;
} }
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems; return static_cast<emphasis>(style_ >> 54);
} }
private: private:
FMT_CONSTEXPR text_style(bool is_foreground, FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style; -> text_style;
@ -303,23 +341,19 @@ class text_style {
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style; -> text_style;
detail::color_type foreground_color; uint64_t style_ = 0;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
}; };
/// Creates a text style from the foreground (text) color. /// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style { -> text_style {
return text_style(true, foreground); return foreground.value_;
} }
/// Creates a text style from the background color. /// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style { -> text_style {
return text_style(false, background); return static_cast<uint64_t>(background.value_) << 27;
} }
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
@ -330,41 +364,39 @@ FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
namespace detail { namespace detail {
template <typename Char> struct ansi_color_escape { template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, FMT_CONSTEXPR ansi_color_escape(color_type text_color,
const char* esc) noexcept { const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code // If we have a terminal color, we need to output another escape code
// sequence. // sequence.
if (!text_color.is_rgb) { if (text_color.is_terminal_color()) {
bool is_background = esc == string_view("\x1b[48;2;"); bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color; uint32_t value = text_color.value();
// Background ASCII codes are the same as the foreground ones but with // Background ASCII codes are the same as the foreground ones but with
// 10 more. // 10 more.
if (is_background) value += 10u; if (is_background) value += 10u;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) { if (value >= 100u) {
buffer[index++] = static_cast<Char>('1'); buffer[size++] = static_cast<Char>('1');
value %= 100u; value %= 100u;
} }
buffer[index++] = static_cast<Char>('0' + value / 10u); buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u); buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m'); buffer[size++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return; return;
} }
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]); buffer[i] = static_cast<Char>(esc[i]);
} }
rgb color(text_color.value.rgb_color); rgb color(text_color.value());
to_esc(color.r, buffer + 7, ';'); to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';'); to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm'); to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0); size = 19;
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {}; uint8_t em_codes[num_emphases] = {};
@ -377,26 +409,28 @@ template <typename Char> struct ansi_color_escape {
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) { for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('['); buffer[size++] = static_cast<Char>(';');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
} }
buffer[index++] = static_cast<Char>(0);
buffer[size - 1] = static_cast<Char>('m');
} }
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + basic_string_view<Char>(buffer).size(); return buffer + size;
} }
private: private:
static constexpr size_t num_emphases = 8; static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u]; Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept { char delimiter) noexcept {
@ -412,13 +446,13 @@ template <typename Char> struct ansi_color_escape {
}; };
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
-> ansi_color_escape<Char> { -> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;"); return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
} }
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
-> ansi_color_escape<Char> { -> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;"); return ansi_color_escape<Char>(background, "\x1b[48;2;");
} }
@ -434,40 +468,33 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
buffer.append(reset_color.begin(), reset_color.end()); buffer.append(reset_color.begin(), reset_color.end());
} }
template <typename T> struct styled_arg : detail::view { template <typename T> struct styled_arg : view {
const T& value; const T& value;
text_style style; text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {} styled_arg(const T& v, text_style s) : value(v), style(s) {}
}; };
template <typename Char> template <typename Char>
void vformat_to( void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str, basic_format_args<buffered_context<Char>> args) {
basic_format_args<buffered_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true; auto emphasis = make_emphasis<Char>(ts.get_emphasis());
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end()); buf.append(emphasis.begin(), emphasis.end());
} }
if (ts.has_foreground()) { if (ts.has_foreground()) {
has_style = true; auto foreground = make_foreground_color<Char>(ts.get_foreground());
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end()); buf.append(foreground.begin(), foreground.end());
} }
if (ts.has_background()) { if (ts.has_background()) {
has_style = true; auto background = make_background_color<Char>(ts.get_background());
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end()); buf.append(background.begin(), background.end());
} }
detail::vformat_to(buf, format_str, args, {}); vformat_to(buf, fmt, args);
if (has_style) detail::reset_color<Char>(buf); if (ts != text_style()) reset_color<Char>(buf);
} }
} // namespace detail } // namespace detail
inline void vprint(FILE* f, const text_style& ts, string_view fmt, inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
format_args args) {
auto buf = memory_buffer(); auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
@ -483,9 +510,8 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
* "Elapsed time: {0:.2f} seconds", 1.23); * "Elapsed time: {0:.2f} seconds", 1.23);
*/ */
template <typename... T> template <typename... T>
void print(FILE* f, const text_style& ts, format_string<T...> fmt, void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
T&&... args) { vprint(f, ts, fmt.str, vargs<T...>{{args...}});
vprint(f, ts, fmt, fmt::make_format_args(args...));
} }
/** /**
@ -498,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt,
* "Elapsed time: {0:.2f} seconds", 1.23); * "Elapsed time: {0:.2f} seconds", 1.23);
*/ */
template <typename... T> template <typename... T>
void print(const text_style& ts, format_string<T...> fmt, T&&... args) { void print(text_style ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...); return print(stdout, ts, fmt, std::forward<T>(args)...);
} }
inline auto vformat(const text_style& ts, string_view fmt, format_args args) inline auto vformat(text_style ts, string_view fmt, format_args args)
-> std::string { -> std::string {
auto buf = memory_buffer(); auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
@ -522,16 +548,16 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
* ``` * ```
*/ */
template <typename... T> template <typename... T>
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args) inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
-> std::string { -> std::string {
return fmt::vformat(ts, fmt, fmt::make_format_args(args...)); return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
} }
/// Formats a string with the given text_style and writes the output to `out`. /// Formats a string with the given text_style and writes the output to `out`.
template <typename OutputIt, template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
format_args args) -> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<char>(out); auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
@ -549,9 +575,9 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
*/ */
template <typename OutputIt, typename... T, template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, const text_style& ts, inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
format_string<T...> fmt, T&&... args) -> OutputIt { T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt, fmt::make_format_args(args...)); return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
} }
template <typename T, typename Char> template <typename T, typename Char>
@ -560,31 +586,30 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
const auto& ts = arg.style; const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out(); auto out = ctx.out();
bool has_style = false; bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true; has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis()); auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = std::copy(emphasis.begin(), emphasis.end(), out); out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
} }
if (ts.has_foreground()) { if (ts.has_foreground()) {
has_style = true; has_style = true;
auto foreground = auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground()); detail::make_foreground_color<Char>(ts.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out); out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
} }
if (ts.has_background()) { if (ts.has_background()) {
has_style = true; has_style = true;
auto background = auto background =
detail::make_background_color<Char>(ts.get_background()); detail::make_background_color<Char>(ts.get_background());
out = std::copy(background.begin(), background.end(), out); out = detail::copy<Char>(background.begin(), background.end(), out);
} }
out = formatter<T, Char>::format(value, ctx); out = formatter<T, Char>::format(arg.value, ctx);
if (has_style) { if (has_style) {
auto reset_color = string_view("\x1b[0m"); auto reset_color = string_view("\x1b[0m");
out = std::copy(reset_color.begin(), reset_color.end(), out); out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
} }
return out; return out;
} }

View File

@ -15,17 +15,10 @@
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
// A compile-time string which is compiled into fast formatting code. // A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {}; class compiled_string {};
namespace detail {
template <typename T, typename InputIt>
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
-> counting_iterator {
return it + (end - begin);
}
template <typename S> template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {}; struct is_compiled_string : std::is_base_of<compiled_string, S> {};
@ -42,34 +35,47 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
* std::string s = fmt::format(FMT_COMPILE("{}"), 42); * std::string s = fmt::format(FMT_COMPILE("{}"), 42);
*/ */
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit) # define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
#else #else
# define FMT_COMPILE(s) FMT_STRING(s) # define FMT_COMPILE(s) FMT_STRING(s)
#endif #endif
/**
* Converts a string literal into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires support
* for class types in constant template parameters (a C++20 feature).
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS #if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N, inline namespace literals {
fmt::detail_exported::fixed_string<Char, N> Str> template <detail::fixed_string Str> constexpr auto operator""_cf() {
struct udl_compiled_string : compiled_string { return FMT_COMPILE(Str.data);
using char_type = Char; }
explicit constexpr operator basic_string_view<char_type>() const { } // namespace literals
return {Str.data, N - 1};
}
};
#endif #endif
FMT_END_EXPORT
namespace detail {
template <typename T, typename... Tail> template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& { constexpr auto first(const T& value, const Tail&...) -> const T& {
return value; return value;
} }
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {}; template <typename... T> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args> template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first, constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) { [[maybe_unused]] const Args&... rest) -> const auto& {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
@ -77,9 +83,32 @@ constexpr const auto& get([[maybe_unused]] const T& first,
return detail::get<N - 1>(rest...); return detail::get<N - 1>(rest...);
} }
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <int N, typename T, typename... Args, typename Char>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
if constexpr (is_static_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
(void)name; // Workaround an MSVC bug about "unused" parameter.
return -1;
}
# endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
# endif
(void)name;
return -1;
}
template <typename Char, typename... Args> template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name, constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) { type_list<Args...>) -> int {
return get_arg_index_by_name<Args...>(name); return get_arg_index_by_name<Args...>(name);
} }
@ -99,8 +128,8 @@ template <typename Char> struct text {
basic_string_view<Char> data; basic_string_view<Char> data;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, data); return write<Char>(out, data);
} }
}; };
@ -109,8 +138,8 @@ template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {}; struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char> template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos, constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
size_t size) { -> text<Char> {
return {{&s[pos], size}}; return {{&s[pos], size}};
} }
@ -118,8 +147,8 @@ template <typename Char> struct code_unit {
Char value; Char value;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
*out++ = value; *out++ = value;
return out; return out;
} }
@ -127,7 +156,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`. // This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args> template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) { constexpr auto get_arg_checked(const Args&... args) -> const T& {
const auto& arg = detail::get<N>(args...); const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value; return arg.value;
@ -140,17 +169,18 @@ template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {}; struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N. // A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field { template <typename Char, typename V, int N> struct field {
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const T& arg = get_arg_checked<T, N>(args...); const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) { if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg); auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out); return copy<Char>(s.begin(), s.end(), out);
} else {
return write<Char>(out, arg);
} }
return write<Char>(out, arg);
} }
}; };
@ -163,10 +193,10 @@ template <typename Char> struct runtime_named_field {
basic_string_view<Char> name; basic_string_view<Char> name;
template <typename OutputIt, typename T> template <typename OutputIt, typename T>
constexpr static bool try_format_argument( constexpr static auto try_format_argument(
OutputIt& out, OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) { [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) { if (arg_name == arg.name) {
out = write<Char>(out, arg.value); out = write<Char>(out, arg.value);
@ -176,8 +206,8 @@ template <typename Char> struct runtime_named_field {
return false; return false;
} }
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
bool found = (try_format_argument(out, name, args) || ...); bool found = (try_format_argument(out, name, args) || ...);
if (!found) { if (!found) {
FMT_THROW(format_error("argument with specified name is not found")); FMT_THROW(format_error("argument with specified name is not found"));
@ -190,17 +220,17 @@ template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {}; struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers. // A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field { template <typename Char, typename V, int N> struct spec_field {
using char_type = Char; using char_type = Char;
formatter<T, Char> fmt; formatter<V, Char> fmt;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr FMT_INLINE OutputIt format(OutputIt out, constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
const Args&... args) const { -> OutputIt {
const auto& vargs = const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...); fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs); basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx); return fmt.format(get_arg_checked<V, N>(args...), ctx);
} }
}; };
@ -212,8 +242,8 @@ template <typename L, typename R> struct concat {
R rhs; R rhs;
using char_type = typename L::char_type; using char_type = typename L::char_type;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
out = lhs.format(out, args...); out = lhs.format(out, args...);
return rhs.format(out, args...); return rhs.format(out, args...);
} }
@ -223,14 +253,14 @@ template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {}; struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R> template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) { constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
return {lhs, rhs}; return {lhs, rhs};
} }
struct unknown_format {}; struct unknown_format {};
template <typename Char> template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) { constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
for (size_t size = str.size(); pos != size; ++pos) { for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break; if (str[pos] == '{' || str[pos] == '}') break;
} }
@ -263,8 +293,8 @@ template <typename T, typename Char> struct parse_specs_result {
enum { manual_indexing_id = -1 }; enum { manual_indexing_id = -1 };
template <typename T, typename Char> template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
size_t pos, int next_arg_id) { int next_arg_id) -> parse_specs_result<T, Char> {
str.remove_prefix(pos); str.remove_prefix(pos);
auto ctx = auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id); compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
@ -275,32 +305,36 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
} }
template <typename Char> struct arg_id_handler { template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
constexpr int on_auto() { constexpr auto on_auto() -> int {
FMT_ASSERT(false, "handler cannot be used with automatic indexing"); FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0; return 0;
} }
constexpr int on_index(int id) { constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr int on_name(basic_string_view<Char> id) { constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
}; };
template <typename Char> struct parse_arg_id_result { template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
const Char* arg_id_end; const Char* arg_id_end;
}; };
template <int ID, typename Char> template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) { constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}}; auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler); auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
} }
template <typename T, typename Enable = void> struct field_type { template <typename T, typename Enable = void> struct field_type {
@ -363,18 +397,18 @@ constexpr auto compile_format_string(S fmt) {
constexpr char_type c = constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string"); static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { if constexpr (arg_id_result.kind == arg_id_kind::index) {
static_assert( static_assert(
ID == manual_indexing_id || ID == 0, ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index; constexpr auto arg_index = arg_id_result.arg_id.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>, return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos, Args, arg_id_end_pos,
arg_index, manual_indexing_id>( arg_index, manual_indexing_id>(
fmt); fmt);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { } else if constexpr (arg_id_result.kind == arg_id_kind::name) {
constexpr auto arg_index = constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
if constexpr (arg_index >= 0) { if constexpr (arg_index >= 0) {
constexpr auto next_id = constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
@ -383,8 +417,7 @@ constexpr auto compile_format_string(S fmt) {
arg_index, next_id>(fmt); arg_index, next_id>(fmt);
} else if constexpr (c == '}') { } else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>( return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
fmt);
} else if constexpr (c == ':') { } else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing return unknown_format(); // no type info for specs parsing
} }
@ -405,7 +438,7 @@ constexpr auto compile_format_string(S fmt) {
} }
template <typename... Args, typename S, template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S fmt) { constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt); constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) { if constexpr (str.size() == 0) {
@ -423,27 +456,28 @@ FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... T,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const Args&... args) { const T&... args)
-> std::basic_string<Char> {
auto s = std::basic_string<Char>(); auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...); cf.format(std::back_inserter(s), args...);
return s; return s;
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, template <typename OutputIt, typename CompiledFormat, typename... T,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const T&... args) -> OutputIt {
return cf.format(out, args...); return cf.format(out, args...);
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
Args&&... args) { -> std::basic_string<typename S::char_type> {
if constexpr (std::is_same<typename S::char_type, char>::value) { if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S()); constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
@ -456,72 +490,97 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
} }
} }
} }
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return fmt::format( return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()), static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return fmt::format(compiled, std::forward<Args>(args)...); return fmt::format(compiled, std::forward<T>(args)...);
} }
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return fmt::format_to( return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()), out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return fmt::format_to(out, compiled, std::forward<Args>(args)...); return fmt::format_to(out, compiled, std::forward<T>(args)...);
} }
} }
#endif #endif
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
-> size_t { auto buf = detail::counting_buffer<>();
return fmt::format_to(detail::counting_iterator(), fmt, args...).count(); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count();
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) { void print(std::FILE* f, const S& fmt, T&&... args) {
memory_buffer buffer; auto buf = memory_buffer();
fmt::format_to(std::back_inserter(buffer), fmt, args...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buffer.data(), buffer.size()}); detail::print(f, {buf.data(), buf.size()});
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) { void print(const S& fmt, T&&... args) {
print(stdout, fmt, args...); print(stdout, fmt, std::forward<T>(args)...);
} }
#if FMT_USE_NONTYPE_TEMPLATE_ARGS template <size_t N> class static_format_result {
inline namespace literals { private:
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() { char data[N];
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t), public:
Str>(); template <typename S, typename... T,
} FMT_ENABLE_IF(is_compiled_string<S>::value)>
} // namespace literals explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
#endif *fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
FMT_END_EXPORT FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,8 @@
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ # if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \ defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \ (!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
!defined(__wasm__)
# include <fcntl.h> // for O_RDONLY # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1 # define FMT_USE_FCNTL 1
# else # else
@ -118,7 +119,7 @@ FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept; const char* message) noexcept;
} }
FMT_API std::system_error vwindows_error(int error_code, string_view format_str, FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
format_args args); format_args args);
/** /**
@ -135,10 +136,9 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
* **Example**: * **Example**:
* *
* // This throws a system_error with the description * // This throws a system_error with the description
* // cannot open file 'madeup': The system cannot find the file * // cannot open file 'foo': The system cannot find the file specified.
* specified. * // or similar (system message may vary) if the file doesn't exist.
* // or similar (system message may vary). * const char *filename = "foo";
* const char *filename = "madeup";
* LPOFSTRUCT of = LPOFSTRUCT(); * LPOFSTRUCT of = LPOFSTRUCT();
* HFILE file = OpenFile(filename, &of, OF_READ); * HFILE file = OpenFile(filename, &of, OF_READ);
* if (file == HFILE_ERROR) { * if (file == HFILE_ERROR) {
@ -146,10 +146,10 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
* "cannot open file '{}'", filename); * "cannot open file '{}'", filename);
* } * }
*/ */
template <typename... Args> template <typename... T>
std::system_error windows_error(int error_code, string_view message, auto windows_error(int error_code, string_view message, const T&... args)
const Args&... args) { -> std::system_error {
return vwindows_error(error_code, message, fmt::make_format_args(args...)); return vwindows_error(error_code, message, vargs<T...>{{args...}});
} }
// Reports a Windows error without throwing an exception. // Reports a Windows error without throwing an exception.
@ -161,14 +161,6 @@ inline auto system_category() noexcept -> const std::error_category& {
} }
#endif // _WIN32 #endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file. // A buffered file.
class buffered_file { class buffered_file {
private: private:
@ -176,24 +168,24 @@ class buffered_file {
friend class file; friend class file;
explicit buffered_file(FILE* f) : file_(f) {} inline explicit buffered_file(FILE* f) : file_(f) {}
public: public:
buffered_file(const buffered_file&) = delete; buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete; void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file. // Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {} inline buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept; FMT_API ~buffered_file() noexcept;
public: public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) { inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr; other.file_ = nullptr;
} }
auto operator=(buffered_file&& other) -> buffered_file& { inline auto operator=(buffered_file&& other) -> buffered_file& {
close(); close();
file_ = other.file_; file_ = other.file_;
other.file_ = nullptr; other.file_ = nullptr;
@ -207,13 +199,13 @@ class buffered_file {
FMT_API void close(); FMT_API void close();
// Returns the pointer to a FILE object representing this file. // Returns the pointer to a FILE object representing this file.
auto get() const noexcept -> FILE* { return file_; } inline auto get() const noexcept -> FILE* { return file_; }
FMT_API auto descriptor() const -> int; FMT_API auto descriptor() const -> int;
template <typename... T> template <typename... T>
inline void print(string_view fmt, const T&... args) { inline void print(string_view fmt, const T&... args) {
const auto& vargs = fmt::make_format_args(args...); fmt::vargs<T...> vargs = {{args...}};
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs) detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
: fmt::vprint(file_, fmt, vargs); : fmt::vprint(file_, fmt, vargs);
} }
@ -248,7 +240,7 @@ class FMT_API file {
}; };
// Constructs a file object which doesn't represent any file. // Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {} inline file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file. // Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag); file(cstring_view path, int oflag);
@ -257,10 +249,10 @@ class FMT_API file {
file(const file&) = delete; file(const file&) = delete;
void operator=(const file&) = delete; void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw. // Move assignment is not noexcept because close may throw.
auto operator=(file&& other) -> file& { inline auto operator=(file&& other) -> file& {
close(); close();
fd_ = other.fd_; fd_ = other.fd_;
other.fd_ = -1; other.fd_ = -1;
@ -271,7 +263,7 @@ class FMT_API file {
~file() noexcept; ~file() noexcept;
// Returns the file descriptor. // Returns the file descriptor.
auto descriptor() const noexcept -> int { return fd_; } inline auto descriptor() const noexcept -> int { return fd_; }
// Closes the file. // Closes the file.
void close(); void close();
@ -324,9 +316,9 @@ auto getpagesize() -> long;
namespace detail { namespace detail {
struct buffer_size { struct buffer_size {
buffer_size() = default; constexpr buffer_size() = default;
size_t value = 0; size_t value = 0;
auto operator=(size_t val) const -> buffer_size { FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size(); auto bs = buffer_size();
bs.value = val; bs.value = val;
return bs; return bs;
@ -337,7 +329,7 @@ struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC; int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {} constexpr ostream_params() {}
template <typename... T> template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) { ostream_params(T... params, int new_oflag) : ostream_params(params...) {
@ -358,59 +350,47 @@ struct ostream_params {
# endif # endif
}; };
class file_buffer final : public buffer<char> { } // namespace detail
FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
/// A fast buffered output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class ostream : private detail::buffer<char> {
private: private:
file file_; file file_;
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
FMT_API static void grow(buffer<char>& buf, size_t); FMT_API static void grow(buffer<char>& buf, size_t);
public: public:
FMT_API file_buffer(cstring_view path, const ostream_params& params); FMT_API ostream(ostream&& other) noexcept;
FMT_API file_buffer(file_buffer&& other) noexcept; FMT_API ~ostream();
FMT_API ~file_buffer();
void flush() { operator writer() {
detail::buffer<char>& buf = *this;
return buf;
}
inline void flush() {
if (size() == 0) return; if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0])); file_.write(data(), size() * sizeof(data()[0]));
clear(); clear();
} }
void close() {
flush();
file_.close();
}
};
} // namespace detail
constexpr auto buffer_size = detail::buffer_size();
/// A fast output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream {
private:
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T> template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream; friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { buffer_.close(); } inline void close() {
flush();
file_.close();
}
/// Formats `args` according to specifications in `fmt` and writes the /// Formats `args` according to specifications in `fmt` and writes the
/// output to the file. /// output to the file.
template <typename... T> void print(format_string<T...> fmt, T&&... args) { template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...)); vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
} }
}; };

View File

@ -22,66 +22,39 @@
#include "chrono.h" // formatbuf #include "chrono.h" // formatbuf
#ifdef _MSVC_STL_UPDATE
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
#else
# define FMT_MSVC_STL_UPDATE 0
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// Generate a unique explicit instantion in every translation unit using a tag // Generate a unique explicit instantiation in every translation unit using a
// type in an anonymous namespace. // tag type in an anonymous namespace.
namespace { namespace {
struct file_access_tag {}; struct file_access_tag {};
} // namespace } // namespace
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr> template <typename Tag, typename BufType, FILE* BufType::* FileMemberPtr>
class file_access { class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
}; };
#if FMT_MSC_VERSION #if FMT_MSVC_STL_UPDATE
template class file_access<file_access_tag, std::filebuf, template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>; &std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*; auto get_file(std::filebuf&) -> FILE*;
#endif #endif
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
-> bool {
FILE* f = nullptr;
#if FMT_MSC_VERSION && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
#else
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline auto write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) -> bool {
return false;
}
// Write the content of buf to os. // Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing. // It is a separate function rather than a part of vprint to simplify testing.
template <typename Char> template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data(); const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; using unsigned_streamsize = make_unsigned_t<std::streamsize>;
unsigned_streamsize size = buf.size(); unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>()); unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do { do {
@ -92,21 +65,9 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
} while (size != 0); } while (size != 0);
} }
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view { template <typename T> struct streamed_view {
const T& value; const T& value;
}; };
} // namespace detail } // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<. // Formats an object of type T that has an overloaded ostream operator<<.
@ -117,7 +78,11 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
template <typename T, typename Context> template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>(); auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value); auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
auto&& output = std::basic_ostream<Char>(&formatbuf);
output.imbue(std::locale::classic()); // The default is always unlocalized.
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
return formatter<basic_string_view<Char>, Char>::format( return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx); {buffer.data(), buffer.size()}, ctx);
} }
@ -148,24 +113,30 @@ constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value}; return {value};
} }
namespace detail { inline void vprint(std::ostream& os, string_view fmt, format_args args) {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer(); auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buffer, fmt, args);
detail::write_buffer(os, buffer); FILE* f = nullptr;
} #if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
} // namespace detail f = detail::get_file(*buf);
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
FMT_EXPORT template <typename Char> auto* rdbuf = os.rdbuf();
void vprint(std::basic_ostream<Char>& os, if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
basic_string_view<type_identity_t<Char>> format_str, f = sfbuf->file();
typename detail::vformat_args<Char>::type args) { else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
auto buffer = basic_memory_buffer<Char>(); f = fbuf->file();
detail::vformat_to(buffer, format_str, args); #endif
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return; #ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
}
}
#endif
detail::ignore_unused(f);
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }
@ -178,32 +149,17 @@ void vprint(std::basic_ostream<Char>& os,
*/ */
FMT_EXPORT template <typename... T> FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) { void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmt::make_format_args(args...); fmt::vargs<T...> vargs = {{args...}};
if (detail::use_utf8()) if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
vprint(os, fmt, vargs); auto buffer = memory_buffer();
else detail::vformat_to(buffer, fmt.str, vargs);
detail::vprint_directly(os, fmt, vargs); detail::write_buffer(os, buffer);
}
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
} }
FMT_EXPORT template <typename... T> FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) { void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...)); fmt::print(os, FMT_STRING("{}\n"),
} fmt::format(fmt, std::forward<T>(args)...));
FMT_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -9,7 +9,7 @@
#define FMT_PRINTF_H_ #define FMT_PRINTF_H_
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <algorithm> // std::max # include <algorithm> // std::find
# include <limits> // std::numeric_limits # include <limits> // std::numeric_limits
#endif #endif
@ -18,10 +18,6 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context { template <typename Char> class basic_printf_context {
private: private:
basic_appender<Char> out_; basic_appender<Char> out_;
@ -33,8 +29,7 @@ template <typename Char> class basic_printf_context {
public: public:
using char_type = Char; using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>; enum { builtin_types = 1 };
template <typename T> using formatter_type = printf_formatter<T>;
/// Constructs a `printf_context` object. References to the arguments are /// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes. /// stored in the context object so make sure they have appropriate lifetimes.
@ -45,7 +40,7 @@ template <typename Char> class basic_printf_context {
auto out() -> basic_appender<Char> { return out_; } auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {} void advance_to(basic_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; } auto locale() -> locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> { auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id); return args_.get(id);
@ -54,14 +49,30 @@ template <typename Char> class basic_printf_context {
namespace detail { namespace detail {
// Return the result via the out param to workaround gcc bug 77539.
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
for (out = first; out != last; ++out) {
if (*out == value) return true;
}
return false;
}
template <>
inline auto find<false, char>(const char* first, const char* last, char value,
const char*& out) -> bool {
out =
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
return out != nullptr;
}
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
template <bool IsSigned> struct int_checker { template <bool IS_SIGNED> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool { template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = to_unsigned(max_value<int>()); return value <= to_unsigned(max_value<int>());
return value <= max;
} }
static auto fits_in_int(bool) -> bool { return true; } inline static auto fits_in_int(bool) -> bool { return true; }
}; };
template <> struct int_checker<true> { template <> struct int_checker<true> {
@ -69,7 +80,7 @@ template <> struct int_checker<true> {
return value >= (std::numeric_limits<int>::min)() && return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>(); value <= max_value<int>();
} }
static auto fits_in_int(int) -> bool { return true; } inline static auto fits_in_int(int) -> bool { return true; }
}; };
struct printf_precision_handler { struct printf_precision_handler {
@ -77,7 +88,7 @@ struct printf_precision_handler {
auto operator()(T value) -> int { auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big"); report_error("number is too big");
return (std::max)(static_cast<int>(value), 0); return max_of(static_cast<int>(value), 0);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -127,25 +138,19 @@ template <typename T, typename Context> class arg_converter {
using target_type = conditional_t<std::is_same<T, void>::value, U, T>; using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) { if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings. // Extra casts are used to silence warnings.
if (is_signed) { using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<int>(static_cast<target_type>(value)); if (is_signed)
arg_ = detail::make_arg<Context>(n); arg_ = static_cast<int>(static_cast<target_type>(value));
} else { else
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else { } else {
if (is_signed) { // glibc's printf doesn't sign extend arguments of smaller types:
// glibc's printf doesn't sign extend arguments of smaller types: // std::printf("%lld", -42); // prints "4294967254"
// std::printf("%lld", -42); // prints "4294967254" // but we don't have to do the same because it's a UB.
// but we don't have to do the same because it's a UB. if (is_signed)
auto n = static_cast<long long>(value); arg_ = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n); else
} else { arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
} }
} }
@ -172,8 +177,7 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
auto c = static_cast<typename Context::char_type>(value); arg_ = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -194,13 +198,13 @@ class printf_width_handler {
format_specs& specs_; format_specs& specs_;
public: public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned { auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value); auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) { if (detail::is_negative(value)) {
specs_.align = align::left; specs_.set_align(align::left);
width = 0 - width; width = 0 - width;
} }
unsigned int_max = to_unsigned(max_value<int>()); unsigned int_max = to_unsigned(max_value<int>());
@ -234,69 +238,74 @@ class printf_arg_formatter : public arg_formatter<Char> {
void write_null_pointer(bool is_string = false) { void write_null_pointer(bool is_string = false) {
auto s = this->specs; auto s = this->specs;
s.type = presentation_type::none; s.set_type(presentation_type::none);
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s); write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
} }
template <typename T> void write(T value) {
detail::write<Char>(this->out, value, this->specs, this->locale);
}
public: public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s, printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx) context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {} : base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); } void operator()(monostate value) { write(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use // MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead. // std::is_same instead.
if (!std::is_same<T, Char>::value) { if (!std::is_same<T, Char>::value) {
base::operator()(value); write(value);
return; return;
} }
format_specs s = this->specs; format_specs s = this->specs;
if (s.type != presentation_type::none && s.type != presentation_type::chr) { if (s.type() != presentation_type::none &&
s.type() != presentation_type::chr) {
return (*this)(static_cast<int>(value)); return (*this)(static_cast<int>(value));
} }
s.sign = sign::none; s.set_sign(sign::none);
s.alt = false; s.clear_alt();
s.fill = ' '; // Ignore '0' flag for char types. s.set_fill(' '); // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is // align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types // ignored for non-numeric types
if (s.align == align::none || s.align == align::numeric) if (s.align() == align::none || s.align() == align::numeric)
s.align = align::right; s.set_align(align::right);
write<Char>(this->out, static_cast<Char>(value), s); detail::write<Char>(this->out, static_cast<Char>(value), s);
} }
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) { void operator()(T value) {
base::operator()(value); write(value);
} }
void operator()(const char* value) { void operator()(const char* value) {
if (value) if (value)
base::operator()(value); write(value);
else else
write_null_pointer(this->specs.type != presentation_type::pointer); write_null_pointer(this->specs.type() != presentation_type::pointer);
} }
void operator()(const wchar_t* value) { void operator()(const wchar_t* value) {
if (value) if (value)
base::operator()(value); write(value);
else else
write_null_pointer(this->specs.type != presentation_type::pointer); write_null_pointer(this->specs.type() != presentation_type::pointer);
} }
void operator()(basic_string_view<Char> value) { base::operator()(value); } void operator()(basic_string_view<Char> value) { write(value); }
void operator()(const void* value) { void operator()(const void* value) {
if (value) if (value)
base::operator()(value); write(value);
else else
write_null_pointer(); write_null_pointer();
} }
void operator()(typename basic_format_arg<context_type>::handle handle) { void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({}); auto parse_ctx = parse_context<Char>({});
handle.format(parse_ctx, context_); handle.format(parse_ctx, context_);
} }
}; };
@ -305,23 +314,14 @@ template <typename Char>
void parse_flags(format_specs& specs, const Char*& it, const Char* end) { void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) { for (; it != end; ++it) {
switch (*it) { switch (*it) {
case '-': case '-': specs.set_align(align::left); break;
specs.align = align::left; case '+': specs.set_sign(sign::plus); break;
break; case '0': specs.set_fill('0'); break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill = '0';
break;
case ' ': case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space; if (specs.sign() != sign::plus) specs.set_sign(sign::space);
break; break;
case '#': case '#': specs.set_alt(); break;
specs.alt = true; default: return;
break;
default:
return;
} }
} }
} }
@ -339,7 +339,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs,
++it; ++it;
arg_index = value != -1 ? value : max_value<int>(); arg_index = value != -1 ? value : max_value<int>();
} else { } else {
if (c == '0') specs.fill = '0'; if (c == '0') specs.set_fill('0');
if (value != 0) { if (value != 0) {
// Nonzero value means that we parsed width and don't need to // Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now. // parse it or flags again, so return now.
@ -369,43 +369,22 @@ inline auto parse_printf_presentation_type(char c, type t, bool& upper)
using pt = presentation_type; using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) { switch (c) {
case 'd': case 'd': return in(t, integral_set) ? pt::dec : pt::none;
return in(t, integral_set) ? pt::dec : pt::none; case 'o': return in(t, integral_set) ? pt::oct : pt::none;
case 'o': case 'X': upper = true; FMT_FALLTHROUGH;
return in(t, integral_set) ? pt::oct : pt::none; case 'x': return in(t, integral_set) ? pt::hex : pt::none;
case 'X': case 'E': upper = true; FMT_FALLTHROUGH;
upper = true; case 'e': return in(t, float_set) ? pt::exp : pt::none;
FMT_FALLTHROUGH; case 'F': upper = true; FMT_FALLTHROUGH;
case 'x': case 'f': return in(t, float_set) ? pt::fixed : pt::none;
return in(t, integral_set) ? pt::hex : pt::none; case 'G': upper = true; FMT_FALLTHROUGH;
case 'E': case 'g': return in(t, float_set) ? pt::general : pt::none;
upper = true; case 'A': upper = true; FMT_FALLTHROUGH;
FMT_FALLTHROUGH; case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
case 'e': case 'c': return in(t, integral_set) ? pt::chr : pt::none;
return in(t, float_set) ? pt::exp : pt::none; case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'F': case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
upper = true; default: return pt::none;
FMT_FALLTHROUGH;
case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
case 'G':
upper = true;
FMT_FALLTHROUGH;
case 'g':
return in(t, float_set) ? pt::general : pt::none;
case 'A':
upper = true;
FMT_FALLTHROUGH;
case 'a':
return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
} }
} }
@ -415,7 +394,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
using iterator = basic_appender<Char>; using iterator = basic_appender<Char>;
auto out = iterator(buf); auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args); auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format); auto parse_ctx = parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next // Returns the argument with specified index or, if arg_index is -1, the next
// argument. // argument.
@ -424,7 +403,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
arg_index = parse_ctx.next_arg_id(); arg_index = parse_ctx.next_arg_id();
else else
parse_ctx.check_arg_id(--arg_index); parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index); auto arg = context.arg(arg_index);
if (!arg) report_error("argument not found");
return arg;
}; };
const Char* start = parse_ctx.begin(); const Char* start = parse_ctx.begin();
@ -444,7 +425,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start))); write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs(); auto specs = format_specs();
specs.align = align::right; specs.set_align(align::right);
// Parse argument index, flags and width. // Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg); int arg_index = parse_header(it, end, specs, get_arg);
@ -468,9 +449,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto arg = get_arg(arg_index); auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is // For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored // specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) { if (specs.precision >= 0 && is_integral_type(arg.type())) {
// Ignore '0' for non-numeric types or if '-' present. // Ignore '0' for non-numeric types or if '-' present.
specs.fill = ' '; specs.set_fill(' ');
} }
if (specs.precision >= 0 && arg.type() == type::cstring_type) { if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = arg.visit(get_cstring<Char>()); auto str = arg.visit(get_cstring<Char>());
@ -478,15 +459,16 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto nul = std::find(str, str_end, Char()); auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>( auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision)); str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv); arg = sv;
} }
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false; if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
if (specs.fill.template get<Char>() == '0') { if (specs.fill_unit<Char>() == '0') {
if (arg.is_arithmetic() && specs.align != align::left) if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
specs.align = align::numeric; specs.set_align(align::numeric);
else } else {
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-' // Ignore '0' flag for non-numeric types or if '-' flag is also present.
// flag is also present. specs.set_fill(' ');
}
} }
// Parse length and convert the argument to the required type. // Parse length and convert the argument to the required type.
@ -511,44 +493,34 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
convert_arg<long>(arg, t); convert_arg<long>(arg, t);
} }
break; break;
case 'j': case 'j': convert_arg<intmax_t>(arg, t); break;
convert_arg<intmax_t>(arg, t); case 'z': convert_arg<size_t>(arg, t); break;
break; case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L': case 'L':
// printf produces garbage when 'L' is omitted for long double, no // printf produces garbage when 'L' is omitted for long double, no
// need to do the same. // need to do the same.
break; break;
default: default: --it; convert_arg<void>(arg, c);
--it;
convert_arg<void>(arg, c);
} }
// Parse type. // Parse type.
if (it == end) report_error("invalid format string"); if (it == end) report_error("invalid format string");
char type = static_cast<char>(*it++); char type = static_cast<char>(*it++);
if (arg.is_integral()) { if (is_integral_type(arg.type())) {
// Normalize type. // Normalize type.
switch (type) { switch (type) {
case 'i': case 'i':
case 'u': case 'u': type = 'd'; break;
type = 'd';
break;
case 'c': case 'c':
arg.visit(char_converter<basic_printf_context<Char>>(arg)); arg.visit(char_converter<basic_printf_context<Char>>(arg));
break; break;
} }
} }
bool upper = false; bool upper = false;
specs.type = parse_printf_presentation_type(type, arg.type(), upper); specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
if (specs.type == presentation_type::none) if (specs.type() == presentation_type::none)
report_error("invalid format specifier"); report_error("invalid format specifier");
specs.upper = upper; if (upper) specs.set_upper();
start = it; start = it;
@ -583,7 +555,7 @@ inline auto vsprintf(basic_string_view<Char> fmt,
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args); detail::vprintf(buf, fmt, args);
return to_string(buf); return {buf.data(), buf.size()};
} }
/** /**
@ -594,15 +566,19 @@ inline auto vsprintf(basic_string_view<Char> fmt,
* *
* std::string message = fmt::sprintf("The answer is %d", 42); * std::string message = fmt::sprintf("The answer is %d", 42);
*/ */
template <typename S, typename... T, typename Char = char_t<S>> template <typename... T>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { inline auto sprintf(string_view fmt, const T&... args) -> std::string {
return vsprintf(detail::to_string_view(fmt), return vsprintf(fmt, make_printf_args(args...));
fmt::make_format_args<basic_printf_context<Char>>(args...)); }
template <typename... T>
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
-> std::wstring {
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
} }
template <typename Char> template <typename Char>
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt, auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args) -> int { typename vprintf_args<Char>::type args) -> int {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args); detail::vprintf(buf, fmt, args);
size_t size = buf.size(); size_t size = buf.size();
@ -619,17 +595,14 @@ inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
* *
* fmt::fprintf(stderr, "Don't %s!", "panic"); * fmt::fprintf(stderr, "Don't %s!", "panic");
*/ */
template <typename S, typename... T, typename Char = char_t<S>> template <typename... T>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt), return vfprintf(f, fmt, make_printf_args(args...));
make_printf_args<Char>(args...));
} }
template <typename... T>
template <typename Char> FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt, const T&... args) -> int {
typename vprintf_args<Char>::type args) return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
-> int {
return vfprintf(stdout, fmt, args);
} }
/** /**
@ -644,11 +617,6 @@ template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int { inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...)); return vfprintf(stdout, fmt, make_printf_args(args...));
} }
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
}
FMT_END_EXPORT FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -11,7 +11,6 @@
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <initializer_list> # include <initializer_list>
# include <iterator> # include <iterator>
# include <string>
# include <tuple> # include <tuple>
# include <type_traits> # include <type_traits>
# include <utility> # include <utility>
@ -19,6 +18,13 @@
#include "format.h" #include "format.h"
#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
# define FMT_LIFETIMEBOUND [[clang::lifetimebound]]
#else
# define FMT_LIFETIMEBOUND
#endif
FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address")
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT FMT_EXPORT
@ -31,7 +37,7 @@ template <typename T> class is_map {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@ -40,29 +46,16 @@ template <typename T> class is_set {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value; !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
}; };
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload // C array overload
template <typename T, std::size_t N> template <typename T, size_t N>
auto range_begin(const T (&arr)[N]) -> const T* { auto range_begin(const T (&arr)[N]) -> const T* {
return arr; return arr;
} }
template <typename T, std::size_t N> template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N; return arr + N;
} }
@ -76,9 +69,13 @@ struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
// Member function overloads. // Member function overloads.
template <typename T> template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin()); auto range_begin(T&& rng) -> decltype(static_cast<T&&>(rng).begin()) {
return static_cast<T&&>(rng).begin();
}
template <typename T> template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end()); auto range_end(T&& rng) -> decltype(static_cast<T&&>(rng).end()) {
return static_cast<T&&>(rng).end();
}
// ADL overloads. Only participate in overload resolution if member functions // ADL overloads. Only participate in overload resolution if member functions
// are not found. // are not found.
@ -115,21 +112,20 @@ struct has_mutable_begin_end<
// SFINAE properly unless there are distinct types // SFINAE properly unless there are distinct types
int>> : std::true_type {}; int>> : std::true_type {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
template <typename T> template <typename T>
struct is_range_<T, void> struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value || : std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {}; has_mutable_begin_end<T>::value)> {};
# undef FMT_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check. // tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ { template <typename T> class is_tuple_like_ {
template <typename U> template <typename U, typename V = typename std::remove_cv<U>::type>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@ -163,7 +159,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value> template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ { class is_tuple_formattable_ {
public: public:
static constexpr const bool value = false; static constexpr bool value = false;
}; };
template <typename T, typename C> class is_tuple_formattable_<T, C, true> { template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is> template <size_t... Is>
@ -179,7 +175,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
C>::value)...>{})); C>::value)...>{}));
public: public:
static constexpr const bool value = static constexpr bool value =
decltype(check(tuple_index_sequence<T>{}))::value; decltype(check(tuple_index_sequence<T>{}))::value;
}; };
@ -217,7 +213,7 @@ template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>; using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get; using std::get;
template <typename Tuple, typename Char, std::size_t... Is> template <typename Tuple, typename Char, size_t... Is>
auto get_formatters(index_sequence<Is...>) auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>; -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple } // namespace tuple
@ -228,7 +224,7 @@ template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>())); using type = decltype(*detail::range_begin(std::declval<R&>()));
}; };
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> { template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
using type = T&; using type = T&;
}; };
@ -245,14 +241,6 @@ using range_reference_type =
template <typename Range> template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>; using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename T> template <typename T>
struct range_format_kind_ struct range_format_kind_
: std::integral_constant<range_format, : std::integral_constant<range_format,
@ -266,12 +254,12 @@ template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>; using range_format_constant = std::integral_constant<range_format, K>;
// These are not generic lambdas for compatibility with C++11. // These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs { template <typename Char> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) { template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx); f.parse(ctx);
detail::maybe_set_debug_format(f, true); detail::maybe_set_debug_format(f, true);
} }
ParseContext& ctx; parse_context<Char>& ctx;
}; };
template <typename FormatContext> struct format_tuple_element { template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type; using char_type = typename FormatContext::char_type;
@ -290,14 +278,15 @@ template <typename FormatContext> struct format_tuple_element {
} // namespace detail } // namespace detail
FMT_EXPORT
template <typename T> struct is_tuple_like { template <typename T> struct is_tuple_like {
static constexpr const bool value = static constexpr bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value; detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
}; };
FMT_EXPORT
template <typename T, typename C> struct is_tuple_formattable { template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value = static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
detail::is_tuple_formattable_<T, C>::value;
}; };
template <typename Tuple, typename Char> template <typename Tuple, typename Char>
@ -327,11 +316,17 @@ struct formatter<Tuple, Char,
closing_bracket_ = close; closing_bracket_ = close;
} }
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
if (it != ctx.end() && *it != '}') report_error("invalid format specifier"); auto end = ctx.end();
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx}); if (it != end && detail::to_ascii(*it) == 'n') {
++it;
set_brackets({}, {});
set_separator({});
}
if (it != end && *it != '}') report_error("invalid format specifier");
ctx.advance_to(it);
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
return it; return it;
} }
@ -346,44 +341,24 @@ struct formatter<Tuple, Char,
} }
}; };
FMT_EXPORT
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static constexpr const bool value = static constexpr bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value; detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
}; };
namespace detail { namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element> template <typename Char, typename Element>
using range_formatter_type = using range_formatter_type = formatter<remove_cvref_t<Element>, Char>;
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
.map(std::declval<Element>()))>,
Char>;
template <typename R> template <typename R>
using maybe_const_range = using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>; conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char> template <typename R, typename Char>
struct is_formattable_delayed struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {}; : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail } // namespace detail
template <typename...> struct conjunction : std::true_type {}; template <typename...> struct conjunction : std::true_type {};
@ -392,6 +367,7 @@ template <typename P1, typename... Pn>
struct conjunction<P1, Pn...> struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
FMT_EXPORT
template <typename T, typename Char, typename Enable = void> template <typename T, typename Char, typename Enable = void>
struct range_formatter; struct range_formatter;
@ -415,7 +391,7 @@ struct range_formatter<
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
for (; it != end; ++it) buf.push_back(*it); for (; it != end; ++it) buf.push_back(*it);
auto specs = format_specs(); auto specs = format_specs();
specs.type = presentation_type::debug; specs.set_type(presentation_type::debug);
return detail::write<Char>( return detail::write<Char>(
out, basic_string_view<Char>(buf.data(), buf.size()), specs); out, basic_string_view<Char>(buf.data(), buf.size()), specs);
} }
@ -443,8 +419,7 @@ struct range_formatter<
closing_bracket_ = close; closing_bracket_ = close;
} }
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
detail::maybe_set_debug_format(underlying_, true); detail::maybe_set_debug_format(underlying_, true);
@ -486,11 +461,10 @@ struct range_formatter<
template <typename R, typename FormatContext> template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
auto mapper = detail::range_mapper<buffered_context<Char>>();
auto out = ctx.out(); auto out = ctx.out();
auto it = detail::range_begin(range); auto it = detail::range_begin(range);
auto end = detail::range_end(range); auto end = detail::range_end(range);
if (is_debug) return write_debug_string(out, it, end); if (is_debug) return write_debug_string(out, std::move(it), end);
out = detail::copy<Char>(opening_bracket_, out); out = detail::copy<Char>(opening_bracket_, out);
int i = 0; int i = 0;
@ -498,7 +472,7 @@ struct range_formatter<
if (i > 0) out = detail::copy<Char>(separator_, out); if (i > 0) out = detail::copy<Char>(separator_, out);
ctx.advance_to(out); ctx.advance_to(out);
auto&& item = *it; // Need an lvalue auto&& item = *it; // Need an lvalue
out = underlying_.format(mapper.map(item), ctx); out = underlying_.format(item, ctx);
++i; ++i;
} }
out = detail::copy<Char>(closing_bracket_, out); out = detail::copy<Char>(closing_bracket_, out);
@ -521,13 +495,8 @@ struct formatter<
range_format_kind<R, Char>::value != range_format::disabled && range_format_kind<R, Char>::value != range_format::disabled &&
range_format_kind<R, Char>::value != range_format::map && range_format_kind<R, Char>::value != range_format::map &&
range_format_kind<R, Char>::value != range_format::string && range_format_kind<R, Char>::value != range_format::string &&
range_format_kind<R, Char>::value != range_format::debug_string> range_format_kind<R, Char>::value != range_format::debug_string>,
// Workaround a bug in MSVC 2015 and earlier. detail::is_formattable_delayed<R, Char>>::value>> {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>> {
private: private:
using range_type = detail::maybe_const_range<R>; using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_; range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
@ -543,8 +512,7 @@ struct formatter<
detail::string_literal<Char, '}'>{}); detail::string_literal<Char, '}'>{});
} }
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return range_formatter_.parse(ctx); return range_formatter_.parse(ctx);
} }
@ -559,7 +527,9 @@ struct formatter<
template <typename R, typename Char> template <typename R, typename Char>
struct formatter< struct formatter<
R, Char, R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> { enable_if_t<conjunction<
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
detail::is_formattable_delayed<R, Char>>::value>> {
private: private:
using map_type = detail::maybe_const_range<R>; using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>; using element_type = detail::uncvref_type<map_type>;
@ -571,8 +541,7 @@ struct formatter<
public: public:
FMT_CONSTEXPR formatter() {} FMT_CONSTEXPR formatter() {}
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it != end) { if (it != end) {
@ -586,7 +555,7 @@ struct formatter<
} }
ctx.advance_to(it); ctx.advance_to(it);
} }
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx}); detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
return it; return it;
} }
@ -596,12 +565,11 @@ struct formatter<
basic_string_view<Char> open = detail::string_literal<Char, '{'>{}; basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
if (!no_delimiters_) out = detail::copy<Char>(open, out); if (!no_delimiters_) out = detail::copy<Char>(open, out);
int i = 0; int i = 0;
auto mapper = detail::range_mapper<buffered_context<Char>>();
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{}; basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
for (auto&& value : map) { for (auto&& value : map) {
if (i > 0) out = detail::copy<Char>(sep, out); if (i > 0) out = detail::copy<Char>(sep, out);
ctx.advance_to(out); ctx.advance_to(out);
detail::for_each2(formatters_, mapper.map(value), detail::for_each2(formatters_, value,
detail::format_tuple_element<FormatContext>{ detail::format_tuple_element<FormatContext>{
0, ctx, detail::string_literal<Char, ':', ' '>{}}); 0, ctx, detail::string_literal<Char, ':', ' '>{}});
++i; ++i;
@ -631,8 +599,7 @@ struct formatter<
formatter<string_type, Char> underlying_; formatter<string_type, Char> underlying_;
public: public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx); return underlying_.parse(ctx);
} }
@ -673,22 +640,22 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
#endif #endif
formatter<remove_cvref_t<value_type>, Char> value_formatter_; formatter<remove_cvref_t<value_type>, Char> value_formatter_;
using view_ref = conditional_t<std::is_copy_constructible<It>::value, using view = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>&, const join_view<It, Sentinel, Char>,
join_view<It, Sentinel, Char>&&>; join_view<It, Sentinel, Char>>;
public: public:
using nonlocking = void; using nonlocking = void;
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
return value_formatter_.parse(ctx); return value_formatter_.parse(ctx);
} }
template <typename FormatContext> template <typename FormatContext>
auto format(view_ref& value, FormatContext& ctx) const auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {
-> decltype(ctx.out()) { using iter =
auto it = std::forward<view_ref>(value).begin; conditional_t<std::is_copy_constructible<view>::value, It, It&>;
iter it = value.begin;
auto out = ctx.out(); auto out = ctx.out();
if (it == value.end) return out; if (it == value.end) return out;
out = value_formatter_.format(*it, ctx); out = value_formatter_.format(*it, ctx);
@ -703,6 +670,123 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
} }
}; };
FMT_EXPORT
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
const Tuple& tuple;
basic_string_view<Char> sep;
tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Tuple, typename Char>
struct formatter<tuple_join_view<Tuple, Char>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::tuple_size<Tuple>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Tuple, Char>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, std::tuple_size<Tuple>());
}
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, 0>)
-> const Char* {
return ctx.begin();
}
template <size_t N>
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, N>)
-> const Char* {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
report_error("incompatible format specs for tuple elements");
}
#endif
return end;
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
using std::get;
auto out =
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& v) -> all {
return {v.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(value), ctx);
}
};
FMT_BEGIN_EXPORT
/// Returns a view that formats the iterator range `[begin, end)` with elements /// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`. /// separated by `sep`.
template <typename It, typename Sentinel> template <typename It, typename Sentinel>
@ -724,140 +808,25 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
* fmt::print("{:02}", fmt::join(v, ", ")); * fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03 * // Output: 01, 02, 03
*/ */
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& r, string_view sep) auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)), -> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> { decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep}; return {detail::range_begin(r), detail::range_end(r), sep};
} }
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
}
private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
template <typename ParseContext>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
report_error("incompatible format specs for tuple elements");
}
#endif
return end;
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMT_BEGIN_EXPORT
/** /**
* Returns an object that formats `std::tuple` with elements separated by `sep`. * Returns an object that formats `std::tuple` with elements separated by `sep`.
* *
* **Example**: * **Example**:
* *
* auto t = std::tuple<int, char>{1, 'a'}; * auto t = std::tuple<int, char>(1, 'a');
* fmt::print("{}", fmt::join(t, ", ")); * fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a * // Output: 1, a
*/ */
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep)
-> tuple_join_view<char, T...> { -> tuple_join_view<Tuple, char> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@ -15,18 +15,18 @@
# include <atomic> # include <atomic>
# include <bitset> # include <bitset>
# include <complex> # include <complex>
# include <cstdlib>
# include <exception> # include <exception>
# include <functional> // std::reference_wrapper
# include <memory> # include <memory>
# include <thread> # include <thread>
# include <type_traits> # include <type_traits>
# include <typeinfo> # include <typeinfo> // std::type_info
# include <utility> # include <utility> // std::make_index_sequence
# include <vector>
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. // Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L # if FMT_CPLUSPLUS >= 201703L
# if FMT_HAS_INCLUDE(<filesystem>) # if FMT_HAS_INCLUDE(<filesystem>) && \
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
# include <filesystem> # include <filesystem>
# endif # endif
# if FMT_HAS_INCLUDE(<variant>) # if FMT_HAS_INCLUDE(<variant>)
@ -60,35 +60,36 @@
# endif # endif
#endif #endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. #ifdef FMT_CPP_LIB_FILESYSTEM
#ifndef FMT_CPP_LIB_FILESYSTEM // Use the provided definition.
# ifdef __cpp_lib_filesystem #elif defined(__cpp_lib_filesystem)
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem # define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
# else #else
# define FMT_CPP_LIB_FILESYSTEM 0 # define FMT_CPP_LIB_FILESYSTEM 0
# endif
#endif #endif
#ifndef FMT_CPP_LIB_VARIANT #ifdef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant // Use the provided definition.
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant #elif defined(__cpp_lib_variant)
# else # define FMT_CPP_LIB_VARIANT __cpp_lib_variant
# define FMT_CPP_LIB_VARIANT 0 #else
# endif # define FMT_CPP_LIB_VARIANT 0
#endif #endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM #if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p, auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) { const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>) if constexpr (std::is_same_v<Char, char> &&
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace); std::is_same_v<PathChar, wchar_t>) {
else return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf);
} else {
return p.string<Char>(); return p.string<Char>();
}
} }
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
@ -109,9 +110,180 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
} }
} }
#endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T, typename FormatContext>
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
-> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
formatter<std::remove_cv_t<T>, Char> underlying;
maybe_set_debug_format(underlying, true);
return underlying.format(v, ctx);
}
#endif
#if FMT_CPP_LIB_VARIANT
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename Variant, typename Char> class is_variant_formattable {
template <size_t... Is>
static auto check(std::index_sequence<Is...>) -> std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
public:
static constexpr bool value = decltype(check(
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
};
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
char* begin) -> string_view {
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* to = begin + 5; // std::
for (const char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
const char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
return demangled_name_view;
}
template <class OutputIt>
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
-> OutputIt {
const string_view demangled_name(abi_name_view);
for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
}
template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = normalize_libcxx_inline_namespaces(
demangled_name_ptr.get(), demangled_name_ptr.get());
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
return normalize_msvc_abi_name(ti.name(), out);
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
const string_view demangled_name = ti.name();
std::string name_copy(demangled_name.size(), '\0');
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
// front of types
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
name_copy.end());
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
// environments, we need to eliminate both of them.
const string_view normalized_name =
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
return detail::write_bytes<char>(out, normalized_name);
# else
return detail::write_bytes<char>(out, string_view(ti.name()));
# endif
}
#endif // FMT_USE_RTTI
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr bool value = std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value &&
has_flip<T>::value;
};
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr bool value = true;
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>
struct has_format_as_member<
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
: std::true_type {};
} // namespace detail } // namespace detail
FMT_EXPORT template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
#if FMT_CPP_LIB_FILESYSTEM
template <typename Char> struct formatter<std::filesystem::path, Char> { template <typename Char> struct formatter<std::filesystem::path, Char> {
private: private:
format_specs specs_; format_specs specs_;
@ -122,14 +294,16 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
public: public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it == end) return it; if (it == end) return it;
it = detail::parse_align(it, end, specs_); it = detail::parse_align(it, end, specs_);
if (it == end) return it; if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); Char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') { if (it != end && *it == '?') {
debug_ = true; debug_ = true;
++it; ++it;
@ -145,8 +319,8 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
!path_type_ ? p.native() !path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>(); : p.generic_string<std::filesystem::path::value_type>();
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
if (!debug_) { if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string); auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs); return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
@ -174,24 +348,20 @@ class path : public std::filesystem::path {
auto generic_system_string() const -> std::string { return generic_string(); } auto generic_system_string() const -> std::string { return generic_string(); }
}; };
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE template <size_t N, typename Char>
FMT_EXPORT struct formatter<std::bitset<N>, Char>
template <std::size_t N, typename Char> : nested_formatter<basic_string_view<Char>, Char> {
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
private: private:
// Functor because C++11 doesn't support generic lambdas. // This is a functor because C++11 doesn't support generic lambdas.
struct writer { struct writer {
const std::bitset<N>& bs; const std::bitset<N>& bs;
template <typename OutputIt> template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) { for (auto pos = N; pos > 0; --pos)
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0')); out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out; return out;
} }
}; };
@ -200,41 +370,28 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return write_padded(ctx, writer{bs}); return this->write_padded(ctx, writer{bs});
} }
}; };
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::optional<T>, Char, struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> { std::enable_if_t<is_formattable<T, Char>::value>> {
private: private:
formatter<T, Char> underlying_; formatter<std::remove_cv_t<T>, Char> underlying_;
static constexpr basic_string_view<Char> optional = static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l', detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{}; '('>{};
static constexpr basic_string_view<Char> none = static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{}; detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public: public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
maybe_set_debug_format(underlying_, true); detail::maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx); return underlying_.parse(ctx);
} }
@ -250,37 +407,15 @@ struct formatter<std::optional<T>, Char,
return detail::write(out, ')'); return detail::write(out, ')');
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_optional #endif // __cpp_lib_optional
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
} // namespace detail
FMT_END_NAMESPACE
#endif
#ifdef __cpp_lib_expected #ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename E, typename Char> template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char, struct formatter<std::expected<T, E>, Char,
std::enable_if_t<is_formattable<T, Char>::value && std::enable_if_t<(std::is_void<T>::value ||
is_formattable<T, Char>::value) &&
is_formattable<E, Char>::value>> { is_formattable<E, Char>::value>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -291,25 +426,21 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) { if (value.has_value()) {
out = detail::write<Char>(out, "expected("); out = detail::write<Char>(out, "expected(");
out = detail::write_escaped_alternative<Char>(out, *value); if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
} else { } else {
out = detail::write<Char>(out, "unexpected("); out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error()); out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
} }
*out++ = ')'; *out++ = ')';
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_expected #endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location #ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> { template <> struct formatter<std::source_location> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
return ctx.begin();
}
template <typename FormatContext> template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const auto format(const std::source_location& loc, FormatContext& ctx) const
@ -325,48 +456,16 @@ template <> struct formatter<std::source_location> {
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif #endif
#if FMT_CPP_LIB_VARIANT #if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
} // namespace detail
template <typename T> struct is_variant_like { template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value; static constexpr bool value = detail::is_variant_like_<T>::value;
}; };
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
};
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> { template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -377,14 +476,12 @@ template <typename Char> struct formatter<std::monostate, Char> {
} }
}; };
FMT_EXPORT
template <typename Variant, typename Char> template <typename Variant, typename Char>
struct formatter< struct formatter<Variant, Char,
Variant, Char, std::enable_if_t<std::conjunction_v<
std::enable_if_t<std::conjunction_v< is_variant_like<Variant>,
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> { detail::is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -397,7 +494,7 @@ struct formatter<
FMT_TRY { FMT_TRY {
std::visit( std::visit(
[&](const auto& v) { [&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v); out = detail::write_escaped_alternative<Char>(out, v, ctx);
}, },
value); value);
} }
@ -408,128 +505,87 @@ struct formatter<
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE template <> struct formatter<std::error_code> {
FMT_EXPORT private:
template <typename Char> struct formatter<std::error_code, Char> { format_specs specs_;
template <typename ParseContext> detail::arg_ref<char> width_ref_;
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { bool debug_ = false;
return ctx.begin();
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
char c = *it;
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && *it == 's') {
specs_.set_type(presentation_type::string);
++it;
}
return it;
} }
template <typename FormatContext> template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const FMT_CONSTEXPR20 auto format(const std::error_code& ec,
-> decltype(ctx.out()) { FormatContext& ctx) const -> decltype(ctx.out()) {
auto out = ctx.out(); auto specs = specs_;
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs()); detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
out = detail::write<Char>(out, Char(':')); ctx);
out = detail::write<Char>(out, ec.value()); auto buf = memory_buffer();
return out; if (specs_.type() == presentation_type::string) {
buf.append(ec.message());
} else {
buf.append(string_view(ec.category().name()));
buf.push_back(':');
detail::write<char>(appender(buf), ec.value());
}
auto quoted = memory_buffer();
auto str = string_view(buf.data(), buf.size());
if (debug_) {
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
str = string_view(quoted.data(), quoted.size());
}
return detail::write<char>(ctx.out(), str, specs);
} }
}; };
#if FMT_USE_RTTI #if FMT_USE_RTTI
namespace detail { template <> struct formatter<std::type_info> {
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
} // namespace detail
FMT_EXPORT
template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
public: public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
-> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
template <typename Context> template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return detail::write_demangled_name<Char>(ctx.out(), ti); return detail::write_demangled_name(ctx.out(), ti);
} }
}; };
#endif #endif // FMT_USE_RTTI
FMT_EXPORT template <typename T>
template <typename T, typename Char>
struct formatter< struct formatter<
T, Char, // DEPRECATED! Mixing code unit types. T, char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> { typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private: private:
bool with_typename_ = false; bool with_typename_ = false;
public: public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
-> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it == end || *it == '}') return it; if (it == end || *it == '}') return it;
@ -546,47 +602,18 @@ struct formatter<
auto out = ctx.out(); auto out = ctx.out();
#if FMT_USE_RTTI #if FMT_USE_RTTI
if (with_typename_) { if (with_typename_) {
out = detail::write_demangled_name<Char>(out, typeid(ex)); out = detail::write_demangled_name(out, typeid(ex));
*out++ = ':'; *out++ = ':';
*out++ = ' '; *out++ = ' ';
} }
#endif #endif
return detail::write_bytes<Char>(out, string_view(ex.what())); return detail::write_bytes<char>(out, string_view(ex.what()));
} }
}; };
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and // We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N // std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization. // in partial specialization.
FMT_EXPORT
template <typename BitRef, typename Char> template <typename BitRef, typename Char>
struct formatter<BitRef, Char, struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>> enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
@ -598,15 +625,6 @@ struct formatter<BitRef, Char,
} }
}; };
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::atomic<T>, Char, struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>> enable_if_t<is_formattable<T, Char>::value>>
@ -619,7 +637,6 @@ struct formatter<std::atomic<T>, Char,
}; };
#ifdef __cpp_lib_atomic_flag_test #ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> { struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext> template <typename FormatContext>
@ -630,7 +647,11 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
}; };
#endif // __cpp_lib_atomic_flag_test #endif // __cpp_lib_atomic_flag_test
FMT_EXPORT template <typename T> struct is_tuple_like;
template <typename T>
struct is_tuple_like<std::complex<T>> : std::false_type {};
template <typename T, typename Char> struct formatter<std::complex<T>, Char> { template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private: private:
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;
@ -643,7 +664,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
if (c.real() != 0) { if (c.real() != 0) {
*out++ = Char('('); *out++ = Char('(');
out = detail::write<Char>(out, c.real(), specs, ctx.locale()); out = detail::write<Char>(out, c.real(), specs, ctx.locale());
specs.sign = sign::plus; specs.set_sign(sign::plus);
out = detail::write<Char>(out, c.imag(), specs, ctx.locale()); out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' '); if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i'); *out++ = Char('i');
@ -657,8 +678,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
} }
public: public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
-> decltype(ctx.begin()) {
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type_constant<T, Char>::value); detail::type_constant<T, Char>::value);
@ -668,12 +688,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto format(const std::complex<T>& c, FormatContext& ctx) const auto format(const std::complex<T>& c, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto specs = specs_; auto specs = specs_;
if (specs.width_ref.kind != detail::arg_id_kind::none || if (specs.dynamic()) {
specs.precision_ref.kind != detail::arg_id_kind::none) { detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
detail::handle_dynamic_spec<detail::width_checker>(specs.width, specs.width_ref, ctx);
specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
detail::handle_dynamic_spec<detail::precision_checker>( specs.precision_ref, ctx);
specs.precision, specs.precision_ref, ctx);
} }
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
@ -681,12 +700,12 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs(); auto outer_specs = format_specs();
outer_specs.width = specs.width; outer_specs.width = specs.width;
outer_specs.fill = specs.fill; outer_specs.copy_fill_from(specs);
outer_specs.align = specs.align; outer_specs.set_align(specs.align());
specs.width = 0; specs.width = 0;
specs.fill = {}; specs.set_fill({});
specs.align = align::none; specs.set_align(align::none);
do_format(c, specs, ctx, basic_appender<Char>(buf)); do_format(c, specs, ctx, basic_appender<Char>(buf));
return detail::write<Char>(ctx.out(), return detail::write<Char>(ctx.out(),
@ -695,5 +714,21 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
} }
}; };
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
// Guard against format_as because reference_wrapper is
// implicitly convertible to T&.
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
!detail::has_format_as<T>::value &&
!detail::has_format_as_member<T>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
}
};
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_STD_H_ #endif // FMT_STD_H_

View File

@ -10,11 +10,12 @@
#include "color.h" #include "color.h"
#include "format.h" #include "format.h"
#include "ostream.h"
#include "ranges.h" #include "ranges.h"
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <cwchar> # include <cwchar>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) # if FMT_USE_LOCALE
# include <locale> # include <locale>
# endif # endif
#endif #endif
@ -34,7 +35,8 @@ struct format_string_char<
}; };
template <typename S> template <typename S>
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> { struct format_string_char<
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
using type = typename S::char_type; using type = typename S::char_type;
}; };
@ -43,7 +45,7 @@ using format_string_char_t = typename format_string_char<S>::type;
inline auto write_loc(basic_appender<wchar_t> out, loc_value value, inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool { const format_specs& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #if FMT_USE_LOCALE
auto& numpunct = auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>()); std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring(); auto separator = std::wstring();
@ -53,36 +55,72 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
#endif #endif
return false; return false;
} }
template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>; using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>; using wformat_parse_context = parse_context<wchar_t>;
using wformat_context = buffered_context<wchar_t>; using wformat_context = buffered_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>; using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>; using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 template <typename Char, typename... T> struct basic_fstring {
// Workaround broken conversion on older gcc. private:
template <typename... Args> using wformat_string = wstring_view; basic_string_view<Char> str_;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else static constexpr int num_static_named_args =
template <typename... Args> detail::count_static_named_args<T...>();
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
using checker = detail::format_string_checker<
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
num_static_named_args != detail::count_named_args<T...>()>;
using arg_pack = detail::arg_pack<T...>;
public:
using t = basic_fstring;
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
if (FMT_USE_CONSTEVAL)
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
}
template <typename S,
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
std::is_same<typename S::char_type, Char>::value)>
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
FMT_CONSTEXPR int ignore =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
}
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
operator basic_string_view<Char>() const { return str_; }
auto get() const -> basic_string_view<Char> { return str_; }
};
template <typename Char, typename... T>
using basic_format_string = basic_fstring<Char, T...>;
template <typename... T>
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> { inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}}; return {{s}};
} }
#endif
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <>
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
#endif
template <typename... T> template <typename... T>
constexpr auto make_wformat_args(T&... args) constexpr auto make_wformat_args(T&... args)
@ -90,14 +128,13 @@ constexpr auto make_wformat_args(T&... args)
return fmt::make_format_args<wformat_context>(args...); return fmt::make_format_args<wformat_context>(args...);
} }
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals { inline namespace literals {
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
constexpr auto operator""_a(const wchar_t* s, size_t)
-> detail::udl_arg<wchar_t> {
return {s}; return {s};
} }
#endif
} // namespace literals } // namespace literals
#endif
template <typename It, typename Sentinel> template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep) auto join(It begin, Sentinel end, wstring_view sep)
@ -105,9 +142,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep}; return {begin, end, sep};
} }
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& range, wstring_view sep) auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>, -> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> { wchar_t> {
return join(std::begin(range), std::end(range), sep); return join(std::begin(range), std::end(range), sep);
} }
@ -118,19 +155,19 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> { -> tuple_join_view<Tuple, wchar_t> {
return {tuple, sep}; return {tuple, sep};
} }
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str, auto vformat(basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args) basic_format_args<buffered_context<Char>> args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, fmt, args);
return to_string(buf); return {buf.data(), buf.size()};
} }
template <typename... T> template <typename... T>
@ -151,40 +188,38 @@ template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value && FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)> !std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> { auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str), return vformat(detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename Locale, typename S, template <typename S, typename Char = detail::format_string_char_t<S>,
typename Char = detail::format_string_char_t<S>, FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& inline auto vformat(locale_ref loc, const S& fmt,
detail::is_exotic_char<Char>::value)> basic_format_args<buffered_context<Char>> args)
inline auto vformat(const Locale& loc, const S& format_str,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), args); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
return {buf.data(), buf.size()};
} }
template <typename Locale, typename S, typename... T, template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto format(locale_ref loc, const S& fmt, T&&... args)
inline auto format(const Locale& loc, const S& format_str, T&&... args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return detail::vformat( return vformat(loc, detail::to_string_view(fmt),
loc, detail::to_string_view(format_str), fmt::make_format_args<buffered_context<Char>>(args...));
fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename OutputIt, typename S, template <typename OutputIt, typename S,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str, auto vformat_to(OutputIt out, const S& fmt,
typename detail::vformat_args<Char>::type args) -> OutputIt { basic_format_args<buffered_context<Char>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args); detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
@ -198,42 +233,37 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename Locale, typename S, typename OutputIt, typename... Args, template <typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str, basic_format_args<buffered_context<Char>> args)
typename detail::vformat_args<Char>::type args)
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args, vformat_to(buf, detail::to_string_view(fmt), args, loc);
detail::locale_ref(loc));
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
template <typename OutputIt, typename Locale, typename S, typename... T, template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value && bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value> detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str, inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
T&&... args) -> -> typename std::enable_if<enable, OutputIt>::type {
typename std::enable_if<enable, OutputIt>::type { return vformat_to(out, loc, detail::to_string_view(fmt),
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename OutputIt, typename Char, typename... Args, template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n, inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
basic_string_view<Char> format_str, basic_format_args<buffered_context<Char>> args)
typename detail::vformat_args<Char>::type args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, fmt, args);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
@ -287,29 +317,33 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...)); return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
} }
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
-> std::wstring { -> std::wstring {
auto buf = wmemory_buffer(); auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
return fmt::to_string(buf); return {buf.data(), buf.size()};
} }
template <typename... T> template <typename... T>
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args) inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring { -> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T> inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, auto buffer = basic_memory_buffer<wchar_t>();
wformat_string<T...> fmt, const T&... args) { detail::vformat_to(buffer, fmt, args);
vprint(f, ts, fmt, fmt::make_wformat_args(args...)); detail::write_buffer(os, buffer);
} }
template <typename... T> template <typename... T>
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt, void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
const T&... args) { vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
return print(stdout, ts, fmt, args...); }
template <typename... T>
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
} }
/// Converts `value` to `std::wstring` using the default format for type `T`. /// Converts `value` to `std::wstring` using the default format for type `T`.

View File

@ -1,5 +1,13 @@
module; module;
#define FMT_MODULE
#ifdef _MSVC_LANG
# define FMT_CPLUSPLUS _MSVC_LANG
#else
# define FMT_CPLUSPLUS __cplusplus
#endif
// Put all implementation-provided headers into the global module fragment // Put all implementation-provided headers into the global module fragment
// to prevent attachment to this module. // to prevent attachment to this module.
#ifndef FMT_IMPORT_STD #ifndef FMT_IMPORT_STD
@ -15,7 +23,9 @@ module;
# include <cstring> # include <cstring>
# include <ctime> # include <ctime>
# include <exception> # include <exception>
# include <expected> # if FMT_CPLUSPLUS > 202002L
# include <expected>
# endif
# include <filesystem> # include <filesystem>
# include <fstream> # include <fstream>
# include <functional> # include <functional>
@ -40,6 +50,8 @@ module;
# include <limits.h> # include <limits.h>
# include <stdint.h> # include <stdint.h>
# include <stdio.h> # include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h> # include <time.h>
#endif #endif
#include <cerrno> #include <cerrno>
@ -127,9 +139,17 @@ extern "C++" {
module :private; module :private;
#endif #endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
#if FMT_HAS_INCLUDE("format.cc") #if FMT_HAS_INCLUDE("format.cc")
# include "format.cc" # include "format.cc"
#endif #endif
#if FMT_OS && FMT_HAS_INCLUDE("os.cc") #if FMT_OS && FMT_HAS_INCLUDE("os.cc")
# include "os.cc" # include "os.cc"
#endif #endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif

View File

@ -8,6 +8,12 @@
#include "fmt/format-inl.h" #include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
namespace detail { namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept template FMT_API auto dragonbox::to_decimal(float x) noexcept
@ -15,28 +21,22 @@ template FMT_API auto dragonbox::to_decimal(float x) noexcept
template FMT_API auto dragonbox::to_decimal(double x) noexcept template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>; -> dragonbox::decimal_fp<double>;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref) template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>; -> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char; template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*); template FMT_API void buffer<char>::append(const char*, const char*);
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref) template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>; -> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
// DEPRECATED!
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*); template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail } // namespace detail

View File

@ -66,14 +66,14 @@ using rwresult = int;
// On Windows the count argument to read and write is unsigned, so convert // On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow. // it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) { inline unsigned convert_rwcount(size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX; return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
} }
#elif FMT_USE_FCNTL #elif FMT_USE_FCNTL
// Return type of read and write functions. // Return type of read and write functions.
using rwresult = ssize_t; using rwresult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; } inline auto convert_rwcount(size_t count) -> size_t { return count; }
#endif #endif
} // namespace } // namespace
@ -160,7 +160,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
} }
void report_windows_error(int error_code, const char* message) noexcept { void report_windows_error(int error_code, const char* message) noexcept {
report_error(detail::format_windows_error, error_code, message); do_report_error(detail::format_windows_error, error_code, message);
} }
#endif // _WIN32 #endif // _WIN32
@ -185,7 +185,7 @@ void buffered_file::close() {
FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
} }
int buffered_file::descriptor() const { auto buffered_file::descriptor() const -> int {
#ifdef FMT_HAS_SYSTEM #ifdef FMT_HAS_SYSTEM
// fileno is a macro on OpenBSD. // fileno is a macro on OpenBSD.
# ifdef fileno # ifdef fileno
@ -240,7 +240,7 @@ void file::close() {
FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
} }
long long file::size() const { auto file::size() const -> long long {
# ifdef _WIN32 # ifdef _WIN32
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
// is less than 0x0500 as is the case with some default MinGW builds. // is less than 0x0500 as is the case with some default MinGW builds.
@ -251,7 +251,7 @@ long long file::size() const {
if (size_lower == INVALID_FILE_SIZE) { if (size_lower == INVALID_FILE_SIZE) {
DWORD error = GetLastError(); DWORD error = GetLastError();
if (error != NO_ERROR) if (error != NO_ERROR)
FMT_THROW(windows_error(GetLastError(), "cannot get file size")); FMT_THROW(windows_error(error, "cannot get file size"));
} }
unsigned long long long_size = size_upper; unsigned long long long_size = size_upper;
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
@ -266,7 +266,7 @@ long long file::size() const {
# endif # endif
} }
std::size_t file::read(void* buffer, std::size_t count) { auto file::read(void* buffer, size_t count) -> size_t {
rwresult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) if (result < 0)
@ -274,7 +274,7 @@ std::size_t file::read(void* buffer, std::size_t count) {
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
std::size_t file::write(const void* buffer, std::size_t count) { auto file::write(const void* buffer, size_t count) -> size_t {
rwresult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) if (result < 0)
@ -282,7 +282,7 @@ std::size_t file::write(const void* buffer, std::size_t count) {
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
file file::dup(int fd) { auto file::dup(int fd) -> file {
// Don't retry as dup doesn't return EINTR. // Don't retry as dup doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd)); int new_fd = FMT_POSIX_CALL(dup(fd));
@ -308,7 +308,7 @@ void file::dup2(int fd, std::error_code& ec) noexcept {
if (result == -1) ec = std::error_code(errno, std::generic_category()); if (result == -1) ec = std::error_code(errno, std::generic_category());
} }
buffered_file file::fdopen(const char* mode) { auto file::fdopen(const char* mode) -> buffered_file {
// Don't retry as fdopen doesn't return EINTR. // Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_) # if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode); FILE* f = ::fdopen(fd_, mode);
@ -355,7 +355,7 @@ pipe::pipe() {
} }
# if !defined(__MSDOS__) # if !defined(__MSDOS__)
long getpagesize() { auto getpagesize() -> long {
# ifdef _WIN32 # ifdef _WIN32
SYSTEM_INFO si; SYSTEM_INFO si;
GetSystemInfo(&si); GetSystemInfo(&si);
@ -374,30 +374,25 @@ long getpagesize() {
} }
# endif # endif
namespace detail { void ostream::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<ostream&>(buf).flush();
void file_buffer::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
} }
file_buffer::file_buffer(cstring_view path, const ostream_params& params) ostream::ostream(cstring_view path, const detail::ostream_params& params)
: buffer<char>(grow), file_(path, params.oflag) { : buffer<char>(grow), file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size); set(new char[params.buffer_size], params.buffer_size);
} }
file_buffer::file_buffer(file_buffer&& other) noexcept ostream::ostream(ostream&& other) noexcept
: buffer<char>(grow, other.data(), other.size(), other.capacity()), : buffer<char>(grow, other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) { file_(std::move(other.file_)) {
other.clear(); other.clear();
other.set(nullptr, 0); other.set(nullptr, 0);
} }
file_buffer::~file_buffer() { ostream::~ostream() {
flush(); flush();
delete[] data(); delete[] data();
} }
} // namespace detail
ostream::~ostream() = default;
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -1 +0,0 @@
7.1.2

View File

@ -1,20 +0,0 @@
cc_library(
name = "fmt",
srcs = [
#"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940)
"src/format.cc",
"src/os.cc",
],
hdrs = glob([
"include/fmt/*.h",
]),
copts = select({
"@platforms//os:windows": ["-utf-8"],
"//conditions:default": [],
}),
includes = [
"include",
],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)

View File

@ -1,6 +0,0 @@
module(
name = "fmt",
compatibility_level = 10,
)
bazel_dep(name = "platforms", version = "0.0.10")

View File

@ -1,28 +0,0 @@
# Bazel support
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`,
`MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project.
This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
## Using {fmt} as a dependency
### Using Bzlmod
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}.
For instance, to use {fmt} add to your `MODULE.bazel` file:
```
bazel_dep(name = "fmt", version = "10.2.1")
```
### Live at head
For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.:
```
local_path_override(
module_name = "fmt",
path = "../third_party/fmt",
)
```

View File

@ -1,2 +0,0 @@
# WORKSPACE marker file needed by Bazel

43
support/check-commits Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Compile source on a range of commits
Usage:
check-commits <start> <source>
"""
import docopt, os, sys, tempfile
from subprocess import check_call, check_output, run
args = docopt.docopt(__doc__)
start = args.get('<start>')
source = args.get('<source>')
cwd = os.getcwd()
with tempfile.TemporaryDirectory() as work_dir:
check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'],
cwd=work_dir)
repo_dir = os.path.join(work_dir, 'fmt')
commits = check_output(
['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit',
'--', 'include', 'src'],
text=True, cwd=repo_dir).rstrip().split('\n')
commits.reverse()
print('Time\tCommit')
for commit in commits:
check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit],
cwd=repo_dir)
returncode = run(
['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include',
'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode
if returncode != 0:
continue
times = []
for i in range(5):
output = check_output([os.path.join(repo_dir, 'a.out')], text=True)
times.append(float(output))
message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit],
cwd=repo_dir, text=True)
print(f'{min(times)}\t{commit} {message[:40]}')
sys.stdout.flush()

View File

@ -2,6 +2,10 @@
# A script to invoke mkdocs with the correct environment. # A script to invoke mkdocs with the correct environment.
# Additionally supports deploying via mike: # Additionally supports deploying via mike:
# ./mkdocs deploy [mike-deploy-options] # ./mkdocs deploy [mike-deploy-options]
# For example:
# ./mkdocs deploy <version>
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
# <version> there.
import errno, os, shutil, sys import errno, os, shutil, sys
from subprocess import call from subprocess import call
@ -15,15 +19,36 @@ path = env.get('PYTHONPATH')
env['PYTHONPATH'] = \ env['PYTHONPATH'] = \
(path + ':' if path else '') + os.path.join(support_dir, 'python') (path + ':' if path else '') + os.path.join(support_dir, 'python')
redirect_page = \
'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting</title>
<noscript>
<meta http-equiv="refresh" content="1; url=11.0/" />
</noscript>
<script>
window.location.replace(
"api/" + window.location.search + window.location.hash
);
</script>
</head>
<body>
Redirecting to <a href="api/">api</a>...
</body>
</html>
'''
config_path = os.path.join(support_dir, 'mkdocs.yml') config_path = os.path.join(support_dir, 'mkdocs.yml')
args = sys.argv[1:] args = sys.argv[1:]
if len(args) > 0: if len(args) > 0:
command = args[0] command = args[0]
if command == 'deploy': if command == 'deploy' or command == 'set-default':
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:' git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
site_repo = git_url + 'fmtlib/fmt.dev.git' site_repo = git_url + 'fmtlib/fmt.dev.git'
site_dir = os. path.join(build_dir, 'fmt.dev') site_dir = os.path.join(build_dir, 'fmt.dev')
try: try:
shutil.rmtree(site_dir) shutil.rmtree(site_dir)
except OSError as e: except OSError as e:
@ -37,8 +62,25 @@ if len(args) > 0:
config_build_path = os.path.join(build_dir, 'mkdocs.yml') config_build_path = os.path.join(build_dir, 'mkdocs.yml')
shutil.copyfile(config_path, config_build_path) shutil.copyfile(config_path, config_build_path)
sys.exit(call(['mike'] + args + ['--config-file', config_build_path, version = args[1]
'--branch', 'master'], cwd=site_dir, env=env)) ret = call(['mike'] + args + ['--config-file', config_build_path,
'--branch', 'master'], cwd=site_dir, env=env)
if ret != 0 or version == 'dev':
sys.exit(ret)
current_doc_path = os.path.join(site_dir, version)
# mike stages files added by deploy for deletion for unclear reason,
# undo it.
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
if False:
os.makedirs(current_doc_path, exist_ok=True)
redirect_page_path = os.path.join(current_doc_path, 'api.html')
with open(redirect_page_path, "w") as file:
file.write(redirect_page)
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
if ret != 0:
sys.exit(ret)
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
sys.exit(ret)
elif not command.startswith('-'): elif not command.startswith('-'):
args += ['-f', config_path] args += ['-f', config_path]
sys.exit(call(['mkdocs'] + args, env=env)) sys.exit(call(['mkdocs'] + args, env=env))

View File

@ -1,317 +1,354 @@
# A basic mkdocstrings handler for {fmt}. # A basic mkdocstrings handler for {fmt}.
# Copyright (c) 2012 - present, Victor Zverovich # Copyright (c) 2012 - present, Victor Zverovich
# https://github.com/fmtlib/fmt/blob/master/LICENSE
import os import os
import xml.etree.ElementTree as ElementTree
from pathlib import Path from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
from typing import Any, List, Mapping, Optional from typing import Any, List, Mapping, Optional
from subprocess import CalledProcessError, PIPE, Popen, STDOUT
import xml.etree.ElementTree as et
from mkdocstrings.handlers.base import BaseHandler from mkdocstrings.handlers.base import BaseHandler
class Definition: class Definition:
'''A definition extracted by Doxygen.''' """A definition extracted by Doxygen."""
def __init__(self, name: str, kind: Optional[str] = None,
node: Optional[et.Element] = None, def __init__(self, name: str, kind: Optional[str] = None,
is_member: bool = False): node: Optional[ElementTree.Element] = None,
self.name = name is_member: bool = False):
self.kind = kind if kind is not None else node.get('kind') self.name = name
self.id = name if not is_member else None self.kind = kind if kind is not None else node.get('kind')
self.params = None self.desc = None
self.members = None self.id = name if not is_member else None
self.members = None
self.params = None
self.template_params = None
self.trailing_return_type = None
self.type = None
# A map from Doxygen to HTML tags. # A map from Doxygen to HTML tags.
tag_map = { tag_map = {
'bold': 'b', 'bold': 'b',
'emphasis': 'em', 'emphasis': 'em',
'computeroutput': 'code', 'computeroutput': 'code',
'para': 'p', 'para': 'p',
'programlisting': 'pre', 'itemizedlist': 'ul',
'verbatim': 'pre' 'listitem': 'li'
} }
# A map from Doxygen tags to text. # A map from Doxygen tags to text.
tag_text_map = { tag_text_map = {
'codeline': '', 'codeline': '',
'highlight': '', 'highlight': '',
'sp': ' ' 'sp': ' '
} }
def escape_html(s: str) -> str: def escape_html(s: str) -> str:
return s.replace("<", "&lt;") return s.replace("<", "&lt;")
def doxyxml2html(nodes: List[et.Element]):
out = ''
for n in nodes:
tag = tag_map.get(n.tag)
if not tag:
out += tag_text_map[n.tag]
out += '<' + tag + '>' if tag else ''
out += '<code class="language-cpp">' if tag == 'pre' else ''
if n.text:
out += escape_html(n.text)
out += doxyxml2html(n)
out += '</code>' if tag == 'pre' else ''
out += '</' + tag + '>' if tag else ''
if n.tail:
out += n.tail
return out
def convert_template_params(node: et.Element) -> Optional[List[Definition]]: # Converts a node from doxygen to HTML format.
templateparamlist = node.find('templateparamlist') def convert_node(node: ElementTree.Element, tag: str, attrs: dict = {}):
if templateparamlist is None: out = '<' + tag
return None for key, value in attrs.items():
params = [] out += ' ' + key + '="' + value + '"'
for param_node in templateparamlist.findall('param'): out += '>'
name = param_node.find('declname') if node.text:
param = Definition(name.text if name is not None else '', 'param') out += escape_html(node.text)
param.type = param_node.find('type').text out += doxyxml2html(list(node))
params.append(param) out += '</' + tag + '>'
return params if node.tail:
out += node.tail
return out
def get_description(node: et.Element) -> List[et.Element]:
return node.findall('briefdescription/para') + \
node.findall('detaileddescription/para')
def normalize_type(type: str) -> str: def doxyxml2html(nodes: List[ElementTree.Element]):
type = type.replace('< ', '<').replace(' >', '>') out = ''
return type.replace(' &', '&').replace(' *', '*') for n in nodes:
tag = tag_map.get(n.tag)
if tag:
out += convert_node(n, tag)
continue
if n.tag == 'programlisting' or n.tag == 'verbatim':
out += '<pre>'
out += convert_node(n, 'code', {'class': 'language-cpp'})
out += '</pre>'
continue
if n.tag == 'ulink':
out += convert_node(n, 'a', {'href': n.attrib['url']})
continue
out += tag_text_map[n.tag]
return out
def convert_type(type: et.Element) -> str:
if type is None:
return None
result = type.text if type.text else ''
for ref in type:
result += ref.text
if ref.tail:
result += ref.tail
result += type.tail.strip()
return normalize_type(result)
def convert_params(func: et.Element) -> Definition: def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
params = [] template_param_list = node.find('templateparamlist')
for p in func.findall('param'): if template_param_list is None:
d = Definition(p.find('declname').text, 'param') return None
d.type = convert_type(p.find('type')) params = []
params.append(d) for param_node in template_param_list.findall('param'):
return params name = param_node.find('declname')
param = Definition(name.text if name is not None else '', 'param')
param.type = param_node.find('type').text
params.append(param)
return params
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
return node.findall('briefdescription/para') + \
node.findall('detaileddescription/para')
def normalize_type(type_: str) -> str:
type_ = type_.replace('< ', '<').replace(' >', '>')
return type_.replace(' &', '&').replace(' *', '*')
def convert_type(type_: ElementTree.Element) -> Optional[str]:
if type_ is None:
return None
result = type_.text if type_.text else ''
for ref in type_:
result += ref.text
if ref.tail:
result += ref.tail
result += type_.tail.strip()
return normalize_type(result)
def convert_params(func: ElementTree.Element) -> List[Definition]:
params = []
for p in func.findall('param'):
d = Definition(p.find('declname').text, 'param')
d.type = convert_type(p.find('type'))
params.append(d)
return params
def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
d.trailing_return_type = None
if d.type == 'auto' or d.type == 'constexpr auto':
parts = node.find('argsstring').text.split(' -> ')
if len(parts) > 1:
d.trailing_return_type = normalize_type(parts[1])
def convert_return_type(d: Definition, node: et.Element) -> None:
d.trailing_return_type = None
if d.type == 'auto' or d.type == 'constexpr auto':
parts = node.find('argsstring').text.split(' -> ')
if len(parts) > 1:
d.trailing_return_type = normalize_type(parts[1])
def render_param(param: Definition) -> str: def render_param(param: Definition) -> str:
return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '') return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '')
def render_decl(d: Definition) -> None:
text = ''
if d.id is not None:
text += f'<a id="{d.id}">\n'
text += '<pre><code class="language-cpp decl">'
text += '<div>' def render_decl(d: Definition) -> str:
if d.template_params is not None: text = ''
text += 'template &lt;'
text += ', '.join([render_param(p) for p in d.template_params])
text += '&gt;\n'
text += '</div>'
text += '<div>'
end = ';'
if d.kind == 'function' or d.kind == 'variable':
text += d.type + ' ' if len(d.type) > 0 else ''
elif d.kind == 'typedef':
text += 'using '
elif d.kind == 'define':
end = ''
else:
text += d.kind + ' '
text += d.name
if d.params is not None:
params = ', '.join([
(p.type + ' ' if p.type else '') + p.name for p in d.params])
text += '(' + escape_html(params) + ')'
if d.trailing_return_type:
text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type)
elif d.kind == 'typedef':
text += ' = ' + escape_html(d.type)
text += end
text += '</div>'
text += '</code></pre>\n'
if d.id is not None:
text += f'</a>\n'
return text
class CxxHandler(BaseHandler):
def __init__(self, **kwargs: Any) -> None:
super().__init__(handler='cxx', **kwargs)
headers = [
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
]
# Run doxygen.
cmd = ['doxygen', '-']
support_dir = Path(__file__).parents[3]
top_dir = os.path.dirname(support_dir)
include_dir = os.path.join(top_dir, 'include', 'fmt')
self._ns2doxyxml = {}
build_dir = os.path.join(top_dir, 'build')
os.makedirs(build_dir, exist_ok=True)
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
_, _ = p.communicate(input=r'''
PROJECT_NAME = fmt
GENERATE_XML = YES
GENERATE_LATEX = NO
GENERATE_HTML = NO
INPUT = {0}
XML_OUTPUT = {1}
QUIET = YES
AUTOLINK_SUPPORT = NO
MACRO_EXPANSION = YES
PREDEFINED = _WIN32=1 \
__linux__=1 \
FMT_ENABLE_IF(...)= \
FMT_USE_USER_DEFINED_LITERALS=1 \
FMT_USE_ALIAS_TEMPLATES=1 \
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
FMT_API= \
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \
"FMT_DOC=1"
'''.format(
' '.join([os.path.join(include_dir, h) for h in headers]),
self._doxyxml_dir).encode('utf-8'))
if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd)
# Merge all file-level XMLs into one to simplify search.
self._file_doxyxml = None
for h in headers:
filename = h.replace(".h", "_8h.xml")
with open(os.path.join(self._doxyxml_dir, filename)) as f:
doxyxml = et.parse(f)
if self._file_doxyxml is None:
self._file_doxyxml = doxyxml
continue
root = self._file_doxyxml.getroot()
for node in doxyxml.getroot():
root.append(node)
def collect_compound(self, identifier: str,
cls: List[et.Element]) -> Definition:
'''Collect a compound definition such as a struct.'''
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
with open(path) as f:
xml = et.parse(f)
node = xml.find('compounddef')
d = Definition(identifier, node=node)
d.template_params = convert_template_params(node)
d.desc = get_description(node)
d.members = []
for m in node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find('name').text
# Doxygen incorrectly classifies members of private unnamed unions as
# public members of the containing class.
if name.endswith('_'):
continue
desc = get_description(m)
if len(desc) == 0:
continue
kind = m.get('kind')
member = Definition(name if name else '', kind=kind, is_member=True)
type = m.find('type').text
member.type = type if type else ''
if kind == 'function':
member.params = convert_params(m)
convert_return_type(member, m)
member.template_params = None
member.desc = desc
d.members.append(member)
return d
def collect(self, identifier: str, config: Mapping[str, Any]) -> Definition:
qual_name = 'fmt::' + identifier
param_str = None
paren = qual_name.find('(')
if paren > 0:
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
colons = qual_name.rfind('::')
namespace, name = qual_name[:colons], qual_name[colons + 2:]
# Load XML.
doxyxml = self._ns2doxyxml.get(namespace)
if doxyxml is None:
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = et.parse(f)
self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
if len(nodes) == 0:
nodes = self._file_doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
candidates = []
for node in nodes:
# Process a function or a typedef.
params = None
d = Definition(name, node=node)
if d.kind == 'function':
params = convert_params(node)
node_param_str = ', '.join([p.type for p in params])
if param_str and param_str != node_param_str:
candidates.append(f'{name}({node_param_str})')
continue
elif d.kind == 'define':
params = []
for p in node.findall('param'):
param = Definition(p.find('defname').text, kind='param')
param.type = None
params.append(param)
d.type = convert_type(node.find('type'))
d.template_params = convert_template_params(node)
d.params = params
convert_return_type(d, node)
d.desc = get_description(node)
return d
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
if not cls:
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
return self.collect_compound(identifier, cls)
def render(self, d: Definition, config: dict) -> str:
if d.id is not None: if d.id is not None:
self.do_heading('', 0, id=d.id) text += f'<a id="{d.id}">\n'
text = '<div class="docblock">\n' text += '<pre><code class="language-cpp decl">'
text += render_decl(d)
text += '<div class="docblock-desc">\n' text += '<div>'
text += doxyxml2html(d.desc) if d.template_params is not None:
if d.members is not None: text += 'template &lt;'
for m in d.members: text += ', '.join([render_param(p) for p in d.template_params])
text += self.render(m, config) text += '&gt;\n'
text += '</div>\n' text += '</div>'
text += '</div>\n'
text += '<div>'
end = ';'
if d.kind == 'function' or d.kind == 'variable':
text += d.type + ' ' if len(d.type) > 0 else ''
elif d.kind == 'typedef':
text += 'using '
elif d.kind == 'define':
end = ''
else:
text += d.kind + ' '
text += d.name
if d.params is not None:
params = ', '.join([
(p.type + ' ' if p.type else '') + p.name for p in d.params])
text += '(' + escape_html(params) + ')'
if d.trailing_return_type:
text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type)
elif d.kind == 'typedef':
text += ' = ' + escape_html(d.type)
text += end
text += '</div>'
text += '</code></pre>\n'
if d.id is not None:
text += f'</a>\n'
return text return text
def get_handler(theme: str, custom_templates: Optional[str] = None,
**config: Any) -> CxxHandler:
'''Return an instance of `CxxHandler`.
Arguments: class CxxHandler(BaseHandler):
theme: The theme to use when rendering contents. def __init__(self, **kwargs: Any) -> None:
custom_templates: Directory containing custom templates. super().__init__(handler='cxx', **kwargs)
**config: Configuration passed to the handler.
''' headers = [
return CxxHandler(theme=theme, custom_templates=custom_templates) 'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
]
# Run doxygen.
cmd = ['doxygen', '-']
support_dir = Path(__file__).parents[3]
top_dir = os.path.dirname(support_dir)
include_dir = os.path.join(top_dir, 'include', 'fmt')
self._ns2doxyxml = {}
build_dir = os.path.join(top_dir, 'build')
os.makedirs(build_dir, exist_ok=True)
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
_, _ = p.communicate(input=r'''
PROJECT_NAME = fmt
GENERATE_XML = YES
GENERATE_LATEX = NO
GENERATE_HTML = NO
INPUT = {0}
XML_OUTPUT = {1}
QUIET = YES
AUTOLINK_SUPPORT = NO
MACRO_EXPANSION = YES
PREDEFINED = _WIN32=1 \
__linux__=1 \
FMT_ENABLE_IF(...)= \
FMT_USE_USER_LITERALS=1 \
FMT_USE_ALIAS_TEMPLATES=1 \
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
FMT_API= \
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \
"FMT_DOC=1"
'''.format(
' '.join([os.path.join(include_dir, h) for h in headers]),
self._doxyxml_dir).encode('utf-8'))
if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd)
# Merge all file-level XMLs into one to simplify search.
self._file_doxyxml = None
for h in headers:
filename = h.replace(".h", "_8h.xml")
with open(os.path.join(self._doxyxml_dir, filename)) as f:
doxyxml = ElementTree.parse(f)
if self._file_doxyxml is None:
self._file_doxyxml = doxyxml
continue
root = self._file_doxyxml.getroot()
for node in doxyxml.getroot():
root.append(node)
def collect_compound(self, identifier: str,
cls: List[ElementTree.Element]) -> Definition:
"""Collect a compound definition such as a struct."""
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
with open(path) as f:
xml = ElementTree.parse(f)
node = xml.find('compounddef')
d = Definition(identifier, node=node)
d.template_params = convert_template_params(node)
d.desc = get_description(node)
d.members = []
for m in \
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find('name').text
# Doxygen incorrectly classifies members of private unnamed unions as
# public members of the containing class.
if name.endswith('_'):
continue
desc = get_description(m)
if len(desc) == 0:
continue
kind = m.get('kind')
member = Definition(name if name else '', kind=kind, is_member=True)
type_text = m.find('type').text
member.type = type_text if type_text else ''
if kind == 'function':
member.params = convert_params(m)
convert_return_type(member, m)
member.template_params = None
member.desc = desc
d.members.append(member)
return d
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
qual_name = 'fmt::' + identifier
param_str = None
paren = qual_name.find('(')
if paren > 0:
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
colons = qual_name.rfind('::')
namespace, name = qual_name[:colons], qual_name[colons + 2:]
# Load XML.
doxyxml = self._ns2doxyxml.get(namespace)
if doxyxml is None:
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = ElementTree.parse(f)
self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
if len(nodes) == 0:
nodes = self._file_doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
candidates = []
for node in nodes:
# Process a function or a typedef.
params = None
d = Definition(name, node=node)
if d.kind == 'function':
params = convert_params(node)
node_param_str = ', '.join([p.type for p in params])
if param_str and param_str != node_param_str:
candidates.append(f'{name}({node_param_str})')
continue
elif d.kind == 'define':
params = []
for p in node.findall('param'):
param = Definition(p.find('defname').text, kind='param')
param.type = None
params.append(param)
d.type = convert_type(node.find('type'))
d.template_params = convert_template_params(node)
d.params = params
convert_return_type(d, node)
d.desc = get_description(node)
return d
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
if not cls:
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
return self.collect_compound(identifier, cls)
def render(self, d: Definition, config: dict) -> str:
if d.id is not None:
self.do_heading('', 0, id=d.id)
text = '<div class="docblock">\n'
text += render_decl(d)
text += '<div class="docblock-desc">\n'
text += doxyxml2html(d.desc)
if d.members is not None:
for m in d.members:
text += self.render(m, config)
text += '</div>\n'
text += '</div>\n'
return text
def get_handler(theme: str, custom_templates: Optional[str] = None,
**_config: Any) -> CxxHandler:
"""Return an instance of `CxxHandler`.
Arguments:
theme: The theme to use when rendering contents.
custom_templates: Directory containing custom templates.
**_config: Configuration passed to the handler.
"""
return CxxHandler(theme=theme, custom_templates=custom_templates)

View File

@ -1,10 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Manage site and releases. """Make a release.
Usage: Usage:
manage.py release [<branch>] release.py [<branch>]
manage.py site
For the release command $FMT_TOKEN should contain a GitHub personal access token For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens. obtained from https://github.com/settings/tokens.
@ -12,9 +11,9 @@ obtained from https://github.com/settings/tokens.
from __future__ import print_function from __future__ import print_function
import datetime, docopt, errno, fileinput, json, os import datetime, docopt, errno, fileinput, json, os
import re, requests, shutil, sys import re, shutil, sys
from contextlib import contextmanager
from subprocess import check_call from subprocess import check_call
import urllib.request
class Git: class Git:
@ -81,46 +80,15 @@ def create_build_env():
return env return env
fmt_repo_url = 'git@github.com:fmtlib/fmt' if __name__ == '__main__':
args = docopt.docopt(__doc__)
def update_site(env):
env.fmt_repo.update(fmt_repo_url)
doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev'))
doc_repo.update('git@github.com:fmtlib/fmt.dev')
version = '11.0.0'
clean_checkout(env.fmt_repo, version)
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
# Build the docs.
html_dir = os.path.join(env.build_dir, 'html')
if os.path.exists(html_dir):
shutil.rmtree(html_dir)
include_dir = env.fmt_repo.dir
import build
build.build_docs(version, doc_dir=target_doc_dir,
include_dir=include_dir, work_dir=env.build_dir)
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
# Copy docs to the website.
version_doc_dir = os.path.join(doc_repo.dir, version)
try:
shutil.rmtree(version_doc_dir)
except OSError as e:
if e.errno != errno.ENOENT:
raise
shutil.move(html_dir, version_doc_dir)
def release(args):
env = create_build_env() env = create_build_env()
fmt_repo = env.fmt_repo fmt_repo = env.fmt_repo
branch = args.get('<branch>') branch = args.get('<branch>')
if branch is None: if branch is None:
branch = 'master' branch = 'master'
if not fmt_repo.update('-b', branch, fmt_repo_url): if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
clean_checkout(fmt_repo, branch) clean_checkout(fmt_repo, branch)
# Update the date in the changelog and extract the version and the first # Update the date in the changelog and extract the version and the first
@ -191,28 +159,30 @@ def release(args):
# Create a release on GitHub. # Create a release on GitHub.
fmt_repo.push('origin', 'release') fmt_repo.push('origin', 'release')
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', req = urllib.request.Request(
headers=auth_headers, 'https://api.github.com/repos/fmtlib/fmt/releases',
data=json.dumps({'tag_name': version, data=json.dumps({'tag_name': version,
'target_commitish': 'release', 'target_commitish': 'release',
'body': changes, 'draft': True})) 'body': changes, 'draft': True}).encode('utf-8'),
if r.status_code != 201: headers=auth_headers, method='POST')
raise Exception('Failed to create a release ' + str(r)) with urllib.request.urlopen(req) as response:
id = r.json()['id'] if response.status != 201:
raise Exception(f'Failed to create a release ' +
'{response.status} {response.reason}')
response_data = json.loads(response.read().decode('utf-8'))
id = response_data['id']
# Upload the package.
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
package = 'fmt-{}.zip'.format(version) package = 'fmt-{}.zip'.format(version)
r = requests.post( req = urllib.request.Request(
'{}/{}/assets?name={}'.format(uploads_url, id, package), f'{uploads_url}/{id}/assets?name={package}',
headers={'Content-Type': 'application/zip'} | auth_headers, headers={'Content-Type': 'application/zip'} | auth_headers,
data=open('build/fmt/' + package, 'rb')) data=open('build/fmt/' + package, 'rb').read(), method='POST')
if r.status_code != 201: with urllib.request.urlopen(req) as response:
raise Exception('Failed to upload an asset ' + str(r)) if response.status != 201:
raise Exception(f'Failed to upload an asset '
'{response.status} {response.reason}')
update_site(env) short_version = '.'.join(version.split('.')[:-1])
check_call(['./mkdocs', 'deploy', short_version])
if __name__ == '__main__':
args = docopt.docopt(__doc__)
if args.get('release'):
release(args)
elif args.get('site'):
update_site(create_build_env())

View File

@ -8,22 +8,6 @@ target_include_directories(test-main PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>) $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_link_libraries(test-main gtest fmt) target_link_libraries(test-main gtest fmt)
function(add_fmt_executable name)
add_executable(${name} ${ARGN})
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
# Bogus -Wstringop-overflow warning
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100395
# [10 Regression] spurious -Wstringop-overflow writing to a trailing array plus offset
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
target_compile_options(${name} PRIVATE -Wno-stringop-overflow)
# The linker flag is needed for LTO.
target_link_libraries(${name} -Wno-stringop-overflow)
endif ()
endfunction()
# Adds a test. # Adds a test.
# Usage: add_fmt_test(name srcs...) # Usage: add_fmt_test(name srcs...)
function(add_fmt_test name) function(add_fmt_test name)
@ -42,7 +26,7 @@ function(add_fmt_test name)
else () else ()
set(libs test-main fmt) set(libs test-main fmt)
endif () endif ()
add_fmt_executable(${name} ${sources}) add_executable(${name} ${sources})
target_link_libraries(${name} ${libs}) target_link_libraries(${name} ${libs})
if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE) if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE)
@ -78,13 +62,9 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif () endif ()
add_fmt_test(ostream-test) add_fmt_test(ostream-test)
add_fmt_test(compile-test) add_fmt_test(compile-test)
add_fmt_test(compile-fp-test HEADER_ONLY)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif()
add_fmt_test(printf-test) add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc) add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(no-builtin-types-test HEADER_ONLY)
add_fmt_test(scan-test HEADER_ONLY) add_fmt_test(scan-test HEADER_ONLY)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME) check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
@ -110,6 +90,9 @@ add_fmt_test(enforce-checks-test)
target_compile_definitions(enforce-checks-test PRIVATE target_compile_definitions(enforce-checks-test PRIVATE
-DFMT_ENFORCE_COMPILE_STRING) -DFMT_ENFORCE_COMPILE_STRING)
add_executable(perf-sanity perf-sanity.cc)
target_link_libraries(perf-sanity fmt::fmt)
if (FMT_MODULE) if (FMT_MODULE)
# The tests need {fmt} to be compiled as traditional library # The tests need {fmt} to be compiled as traditional library
# because of visibility of implementation details. # because of visibility of implementation details.
@ -142,7 +125,7 @@ if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
endif() endif()
if (NOT MSVC_STATIC_RUNTIME) if (NOT MSVC_STATIC_RUNTIME)
add_fmt_executable(posix-mock-test add_executable(posix-mock-test
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC}) posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
target_include_directories( target_include_directories(
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include) posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
@ -233,7 +216,7 @@ if (FMT_PEDANTIC AND NOT WIN32 AND NOT (
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
endif () endif ()
# This test are disabled on Windows because it is only *NIX issue. # This test is disabled on Windows because it is POSIX-specific.
if (FMT_PEDANTIC AND NOT WIN32) if (FMT_PEDANTIC AND NOT WIN32)
add_test(static-export-test ${CMAKE_CTEST_COMMAND} add_test(static-export-test ${CMAKE_CTEST_COMMAND}
-C ${CMAKE_BUILD_TYPE} -C ${CMAKE_BUILD_TYPE}

View File

@ -64,7 +64,7 @@ TEST(args_test, custom_format) {
} }
struct to_stringable { struct to_stringable {
friend fmt::string_view to_string_view(to_stringable) { return {}; } friend auto to_string_view(to_stringable) -> fmt::string_view { return {}; }
}; };
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
@ -186,3 +186,17 @@ TEST(args_test, move_constructor) {
store.reset(); store.reset();
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo"); EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
} }
TEST(args_test, size) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
EXPECT_EQ(store.size(), 0);
store.push_back(42);
EXPECT_EQ(store.size(), 1);
store.push_back("Molybdenum");
EXPECT_EQ(store.size(), 2);
store.clear();
EXPECT_EQ(store.size(), 0);
}

View File

@ -5,14 +5,16 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
// Turn assertion failures into exceptions for testing.
// clang-format off // clang-format off
#include "test-assert.h" #include "test-assert.h"
// clang-format on // clang-format on
#include "fmt/base.h" #include "fmt/base.h"
#include <climits> // INT_MAX #include <limits.h> // INT_MAX
#include <cstring> // std::strlen #include <string.h> // strlen
#include <functional> // std::equal_to #include <functional> // std::equal_to
#include <iterator> // std::back_insert_iterator, std::distance #include <iterator> // std::back_insert_iterator, std::distance
#include <limits> // std::numeric_limits #include <limits> // std::numeric_limits
@ -21,39 +23,36 @@
#include "gmock/gmock.h" #include "gmock/gmock.h"
using fmt::string_view; #ifdef FMT_FORMAT_H_
using fmt::detail::buffer; # error base-test includes format.h
#endif
using testing::_; using testing::_;
using testing::Invoke; using testing::Invoke;
using testing::Return; using testing::Return;
#ifdef FMT_FORMAT_H_ auto copy(fmt::string_view s, fmt::appender out) -> fmt::appender {
# error core-test includes format.h
#endif
fmt::appender copy(fmt::string_view s, fmt::appender out) {
for (char c : s) *out++ = c; for (char c : s) *out++ = c;
return out; return out;
} }
TEST(string_view_test, value_type) { TEST(string_view_test, value_type) {
static_assert(std::is_same<string_view::value_type, char>::value, ""); static_assert(std::is_same<fmt::string_view::value_type, char>::value, "");
} }
TEST(string_view_test, ctor) { TEST(string_view_test, ctor) {
EXPECT_STREQ("abc", fmt::string_view("abc").data()); EXPECT_STREQ(fmt::string_view("abc").data(), "abc");
EXPECT_EQ(3u, fmt::string_view("abc").size()); EXPECT_EQ(fmt::string_view("abc").size(), 3u);
EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data()); EXPECT_STREQ(fmt::string_view(std::string("defg")).data(), "defg");
EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size()); EXPECT_EQ(fmt::string_view(std::string("defg")).size(), 4u);
} }
TEST(string_view_test, length) { TEST(string_view_test, length) {
// Test that string_view::size() returns string length, not buffer size. // Test that string_view::size() returns string length, not buffer size.
char str[100] = "some string"; char str[100] = "some string";
EXPECT_EQ(std::strlen(str), string_view(str).size()); EXPECT_EQ(fmt::string_view(str).size(), strlen(str));
EXPECT_LT(std::strlen(str), sizeof(str)); EXPECT_LT(strlen(str), sizeof(str));
} }
// Check string_view's comparison operator. // Check string_view's comparison operator.
@ -62,13 +61,16 @@ template <template <typename> class Op> void check_op() {
size_t num_inputs = sizeof(inputs) / sizeof(*inputs); size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
for (size_t i = 0; i < num_inputs; ++i) { for (size_t i = 0; i < num_inputs; ++i) {
for (size_t j = 0; j < num_inputs; ++j) { for (size_t j = 0; j < num_inputs; ++j) {
string_view lhs(inputs[i]), rhs(inputs[j]); fmt::string_view lhs(inputs[i]), rhs(inputs[j]);
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs)); EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0),
Op<fmt::string_view>()(lhs, rhs));
} }
} }
} }
TEST(string_view_test, compare) { TEST(string_view_test, compare) {
using fmt::string_view;
EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0); EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
EXPECT_GT(string_view("fop").compare(string_view("foo")), 0); EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0); EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
@ -92,64 +94,49 @@ TEST(string_view_test, compare) {
check_op<std::greater_equal>(); check_op<std::greater_equal>();
} }
TEST(base_test, is_output_iterator) { #if FMT_USE_CONSTEVAL
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value)); TEST(string_view_test, from_constexpr_fixed_string) {
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value)); constexpr int size = 4;
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::string::iterator, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
char>::value));
}
TEST(base_test, is_back_insert_iterator) { struct fixed_string {
EXPECT_TRUE(fmt::detail::is_back_insert_iterator< char data[size] = {};
std::back_insert_iterator<std::string>>::value);
EXPECT_FALSE(fmt::detail::is_back_insert_iterator<
std::front_insert_iterator<std::string>>::value);
}
TEST(base_test, buffer_appender) { constexpr fixed_string(const char (&m)[size]) {
#ifdef __cpp_lib_ranges for (size_t i = 0; i != size; ++i) data[i] = m[i];
static_assert(std::output_iterator<fmt::appender, char>); }
#endif };
}
static constexpr auto fs = fixed_string("foo");
static constexpr auto sv = fmt::string_view(fs.data);
EXPECT_EQ(sv, "foo");
}
#endif // FMT_USE_CONSTEVAL
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
TEST(buffer_test, noncopyable) { TEST(buffer_test, noncopyable) {
EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value); EXPECT_FALSE(std::is_copy_constructible<fmt::detail::buffer<char>>::value);
# if !FMT_MSC_VERSION EXPECT_FALSE(std::is_copy_assignable<fmt::detail::buffer<char>>::value);
// std::is_copy_assignable is broken in MSVC2013.
EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value);
# endif
} }
TEST(buffer_test, nonmoveable) { TEST(buffer_test, nonmoveable) {
EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value); EXPECT_FALSE(std::is_move_constructible<fmt::detail::buffer<char>>::value);
# if !FMT_MSC_VERSION EXPECT_FALSE(std::is_move_assignable<fmt::detail::buffer<char>>::value);
// std::is_move_assignable is broken in MSVC2013.
EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value);
# endif
} }
#endif
TEST(buffer_test, indestructible) { TEST(buffer_test, indestructible) {
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(), static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
"buffer's destructor is protected"); "buffer's destructor is protected");
} }
template <typename T> struct mock_buffer final : buffer<T> { template <typename T> struct mock_buffer final : fmt::detail::buffer<T> {
MOCK_METHOD(size_t, do_grow, (size_t)); MOCK_METHOD(size_t, do_grow, (size_t));
static void grow(buffer<T>& buf, size_t capacity) { static void grow(fmt::detail::buffer<T>& buf, size_t capacity) {
auto& self = static_cast<mock_buffer&>(buf); auto& self = static_cast<mock_buffer&>(buf);
self.set(buf.data(), self.do_grow(capacity)); self.set(buf.data(), self.do_grow(capacity));
} }
mock_buffer(T* data = nullptr, size_t buf_capacity = 0) : buffer<T>(grow) { mock_buffer(T* data = nullptr, size_t buf_capacity = 0)
: fmt::detail::buffer<T>(grow) {
this->set(data, buf_capacity); this->set(data, buf_capacity);
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) { ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
return capacity; return capacity;
@ -160,24 +147,24 @@ template <typename T> struct mock_buffer final : buffer<T> {
TEST(buffer_test, ctor) { TEST(buffer_test, ctor) {
{ {
mock_buffer<int> buffer; mock_buffer<int> buffer;
EXPECT_EQ(nullptr, buffer.data()); EXPECT_EQ(buffer.data(), nullptr);
EXPECT_EQ(static_cast<size_t>(0), buffer.size()); EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity()); EXPECT_EQ(buffer.capacity(), 0u);
} }
{ {
int dummy; int data;
mock_buffer<int> buffer(&dummy); mock_buffer<int> buffer(&data);
EXPECT_EQ(&dummy, &buffer[0]); EXPECT_EQ(&buffer[0], &data);
EXPECT_EQ(static_cast<size_t>(0), buffer.size()); EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity()); EXPECT_EQ(buffer.capacity(), 0u);
} }
{ {
int dummy; int data;
size_t capacity = std::numeric_limits<size_t>::max(); size_t capacity = std::numeric_limits<size_t>::max();
mock_buffer<int> buffer(&dummy, capacity); mock_buffer<int> buffer(&data, capacity);
EXPECT_EQ(&dummy, &buffer[0]); EXPECT_EQ(&buffer[0], &data);
EXPECT_EQ(static_cast<size_t>(0), buffer.size()); EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(capacity, buffer.capacity()); EXPECT_EQ(buffer.capacity(), capacity);
} }
} }
@ -185,26 +172,26 @@ TEST(buffer_test, access) {
char data[10]; char data[10];
mock_buffer<char> buffer(data, sizeof(data)); mock_buffer<char> buffer(data, sizeof(data));
buffer[0] = 11; buffer[0] = 11;
EXPECT_EQ(11, buffer[0]); EXPECT_EQ(buffer[0], 11);
buffer[3] = 42; buffer[3] = 42;
EXPECT_EQ(42, *(&buffer[0] + 3)); EXPECT_EQ(*(&buffer[0] + 3), 42);
const fmt::detail::buffer<char>& const_buffer = buffer; const fmt::detail::buffer<char>& const_buffer = buffer;
EXPECT_EQ(42, const_buffer[3]); EXPECT_EQ(const_buffer[3], 42);
} }
TEST(buffer_test, try_resize) { TEST(buffer_test, try_resize) {
char data[123]; char data[123];
mock_buffer<char> buffer(data, sizeof(data)); mock_buffer<char> buffer(data, sizeof(data));
buffer[10] = 42; buffer[10] = 42;
EXPECT_EQ(42, buffer[10]); EXPECT_EQ(buffer[10], 42);
buffer.try_resize(20); buffer.try_resize(20);
EXPECT_EQ(20u, buffer.size()); EXPECT_EQ(buffer.size(), 20u);
EXPECT_EQ(123u, buffer.capacity()); EXPECT_EQ(buffer.capacity(), 123u);
EXPECT_EQ(42, buffer[10]); EXPECT_EQ(buffer[10], 42);
buffer.try_resize(5); buffer.try_resize(5);
EXPECT_EQ(5u, buffer.size()); EXPECT_EQ(buffer.size(), 5u);
EXPECT_EQ(123u, buffer.capacity()); EXPECT_EQ(buffer.capacity(), 123u);
EXPECT_EQ(42, buffer[10]); EXPECT_EQ(buffer[10], 42);
// Check if try_resize calls grow. // Check if try_resize calls grow.
EXPECT_CALL(buffer, do_grow(124)); EXPECT_CALL(buffer, do_grow(124));
buffer.try_resize(124); buffer.try_resize(124);
@ -226,8 +213,8 @@ TEST(buffer_test, clear) {
EXPECT_CALL(buffer, do_grow(20)); EXPECT_CALL(buffer, do_grow(20));
buffer.try_resize(20); buffer.try_resize(20);
buffer.try_resize(0); buffer.try_resize(0);
EXPECT_EQ(static_cast<size_t>(0), buffer.size()); EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(20u, buffer.capacity()); EXPECT_EQ(buffer.capacity(), 20u);
} }
TEST(buffer_test, append) { TEST(buffer_test, append) {
@ -235,14 +222,14 @@ TEST(buffer_test, append) {
mock_buffer<char> buffer(data, 10); mock_buffer<char> buffer(data, 10);
auto test = "test"; auto test = "test";
buffer.append(test, test + 5); buffer.append(test, test + 5);
EXPECT_STREQ(test, &buffer[0]); EXPECT_STREQ(&buffer[0], test);
EXPECT_EQ(5u, buffer.size()); EXPECT_EQ(buffer.size(), 5u);
buffer.try_resize(10); buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(12)); EXPECT_CALL(buffer, do_grow(12));
buffer.append(test, test + 2); buffer.append(test, test + 2);
EXPECT_EQ('t', buffer[10]); EXPECT_EQ(buffer[10], 't');
EXPECT_EQ('e', buffer[11]); EXPECT_EQ(buffer[11], 'e');
EXPECT_EQ(12u, buffer.size()); EXPECT_EQ(buffer.size(), 12u);
} }
TEST(buffer_test, append_partial) { TEST(buffer_test, append_partial) {
@ -268,27 +255,51 @@ TEST(buffer_test, append_allocates_enough_storage) {
buffer.append(test, test + 9); buffer.append(test, test + 9);
} }
struct custom_context { TEST(base_test, is_locking) {
using char_type = char; EXPECT_FALSE(fmt::detail::is_locking<const char(&)[3]>());
using parse_context_type = fmt::format_parse_context; }
bool called = false; TEST(base_test, is_output_iterator) {
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::string::iterator, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
char>::value));
}
template <typename T> struct formatter_type { TEST(base_test, is_back_insert_iterator) {
FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx) EXPECT_TRUE(fmt::detail::is_back_insert_iterator<
-> decltype(ctx.begin()) { std::back_insert_iterator<std::string>>::value);
return ctx.begin(); EXPECT_FALSE(fmt::detail::is_back_insert_iterator<
} std::front_insert_iterator<std::string>>::value);
}
const char* format(const T&, custom_context& ctx) const { struct minimal_container {
ctx.called = true; using value_type = char;
return nullptr; void push_back(char) {}
}
};
void advance_to(const char*) {}
}; };
TEST(base_test, copy) {
minimal_container c;
static constexpr char str[] = "a";
fmt::detail::copy<char>(str, str + 1, std::back_inserter(c));
}
TEST(base_test, get_buffer) {
mock_buffer<char> buffer;
void* buffer_ptr = &buffer;
auto&& appender_result = fmt::detail::get_buffer<char>(fmt::appender(buffer));
EXPECT_EQ(&appender_result, buffer_ptr);
auto&& back_inserter_result =
fmt::detail::get_buffer<char>(std::back_inserter(buffer));
EXPECT_EQ(&back_inserter_result, buffer_ptr);
}
struct test_struct {}; struct test_struct {};
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
@ -303,21 +314,6 @@ template <typename Char> struct formatter<test_struct, Char> {
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(arg_test, format_args) {
auto args = fmt::format_args();
EXPECT_FALSE(args.get(1));
}
TEST(arg_test, make_value_with_custom_context) {
auto t = test_struct();
auto arg = fmt::detail::value<custom_context>(
fmt::detail::arg_mapper<custom_context>().map(t));
auto ctx = custom_context();
auto parse_ctx = fmt::format_parse_context("");
arg.custom.format(&t, parse_ctx, ctx);
EXPECT_TRUE(ctx.called);
}
// Use a unique result type to make sure that there are no undesirable // Use a unique result type to make sure that there are no undesirable
// conversions. // conversions.
struct test_result {}; struct test_result {};
@ -364,29 +360,99 @@ VISIT_TYPE(long, long long);
VISIT_TYPE(unsigned long, unsigned long long); VISIT_TYPE(unsigned long, unsigned long long);
#endif #endif
#define CHECK_ARG(Char, expected, value) \ #if FMT_BUILTIN_TYPES
{ \ # define CHECK_ARG(expected, value) \
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \ { \
EXPECT_CALL(visitor, visit(expected)); \ testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
using iterator = fmt::basic_appender<Char>; \ EXPECT_CALL(visitor, visit(expected)); \
auto var = value; \ auto var = value; \
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>(var) \ fmt::basic_format_arg<fmt::format_context>(var).visit(visitor); \
.visit(visitor); \ }
} #else
# define CHECK_ARG(expected, value)
#endif
#define CHECK_ARG_SIMPLE(value) \ #define CHECK_ARG_SIMPLE(value) \
{ \ { \
using value_type = decltype(value); \ using value_type = decltype(value); \
typename visit_type<value_type>::type expected = value; \ typename visit_type<value_type>::type expected = value; \
CHECK_ARG(char, expected, value) \ CHECK_ARG(expected, value) \
} }
TEST(arg_test, format_args) {
auto args = fmt::format_args();
EXPECT_FALSE(args.get(1));
}
TEST(arg_test, char_arg) { CHECK_ARG('a', 'a'); }
TEST(arg_test, string_arg) {
char str_data[] = "test";
char* str = str_data;
const char* cstr = str;
CHECK_ARG(cstr, str);
auto sv = fmt::string_view(str);
CHECK_ARG(sv, std::string(str));
}
TEST(arg_test, pointer_arg) {
void* p = nullptr;
const void* cp = nullptr;
CHECK_ARG(cp, p);
CHECK_ARG_SIMPLE(cp);
}
TEST(arg_test, volatile_pointer_arg) {
const void* p = nullptr;
volatile int* vip = nullptr;
const volatile int* cvip = nullptr;
CHECK_ARG(p, static_cast<volatile void*>(vip));
CHECK_ARG(p, static_cast<const volatile void*>(cvip));
}
struct check_custom {
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
-> test_result {
struct test_buffer final : fmt::detail::buffer<char> {
char data[10];
test_buffer()
: fmt::detail::buffer<char>([](buffer<char>&, size_t) {}, data, 0,
10) {}
} buffer;
auto parse_ctx = fmt::format_parse_context("");
auto ctx = fmt::format_context(fmt::appender(buffer), fmt::format_args());
h.format(parse_ctx, ctx);
EXPECT_EQ(std::string(buffer.data, buffer.size()), "test");
return test_result();
}
};
TEST(arg_test, custom_arg) {
auto test = test_struct();
using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
auto&& v = testing::StrictMock<visitor>();
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
fmt::basic_format_arg<fmt::format_context>(test).visit(v);
}
TEST(arg_test, visit_invalid_arg) {
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
EXPECT_CALL(visitor, visit(_));
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
}
template <typename T> class numeric_arg_test : public testing::Test {}; template <typename T> class numeric_arg_test : public testing::Test {};
#if FMT_BUILTIN_TYPES
using test_types = using test_types =
testing::Types<bool, signed char, unsigned char, short, unsigned short, int, testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
unsigned, long, unsigned long, long long, unsigned long long, unsigned, long, unsigned long, long long, unsigned long long,
float, double, long double>; float, double, long double>;
#else
using test_types = testing::Types<int>;
#endif
TYPED_TEST_SUITE(numeric_arg_test, test_types); TYPED_TEST_SUITE(numeric_arg_test, test_types);
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0> template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
@ -406,88 +472,34 @@ TYPED_TEST(numeric_arg_test, make_and_visit) {
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max()); CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
} }
TEST(arg_test, char_arg) { CHECK_ARG(char, 'a', 'a'); }
TEST(arg_test, string_arg) {
char str_data[] = "test";
char* str = str_data;
const char* cstr = str;
CHECK_ARG(char, cstr, str);
auto sv = fmt::string_view(str);
CHECK_ARG(char, sv, std::string(str));
}
TEST(arg_test, pointer_arg) {
void* p = nullptr;
const void* cp = nullptr;
CHECK_ARG(char, cp, p);
CHECK_ARG_SIMPLE(cp);
}
struct check_custom {
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
-> test_result {
struct test_buffer final : fmt::detail::buffer<char> {
char data[10];
test_buffer()
: fmt::detail::buffer<char>([](buffer<char>&, size_t) {}, data, 0,
10) {}
} buffer;
auto parse_ctx = fmt::format_parse_context("");
auto ctx = fmt::format_context(fmt::appender(buffer), fmt::format_args());
h.format(parse_ctx, ctx);
EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
return test_result();
}
};
TEST(arg_test, custom_arg) {
auto test = test_struct();
using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
auto&& v = testing::StrictMock<visitor>();
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
fmt::detail::make_arg<fmt::format_context>(test).visit(v);
}
TEST(arg_test, visit_invalid_arg) {
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
EXPECT_CALL(visitor, visit(_));
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
}
#if FMT_USE_CONSTEXPR #if FMT_USE_CONSTEXPR
enum class arg_id_result { none, empty, index, name }; enum class arg_id_result { none, index, name };
struct test_arg_id_handler { struct test_arg_id_handler {
arg_id_result res = arg_id_result::none; arg_id_result res = arg_id_result::none;
int index = 0; int index = 0;
string_view name; fmt::string_view name;
constexpr void on_auto() { res = arg_id_result::empty; }
constexpr void on_index(int i) { constexpr void on_index(int i) {
res = arg_id_result::index; res = arg_id_result::index;
index = i; index = i;
} }
constexpr void on_name(string_view n) { constexpr void on_name(fmt::string_view n) {
res = arg_id_result::name; res = arg_id_result::name;
name = n; name = n;
} }
}; };
template <size_t N> template <size_t N>
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) { constexpr auto parse_arg_id(const char (&s)[N]) -> test_arg_id_handler {
auto h = test_arg_id_handler(); auto h = test_arg_id_handler();
fmt::detail::parse_arg_id(s, s + N, h); fmt::detail::parse_arg_id(s, s + N, h);
return h; return h;
} }
TEST(base_test, constexpr_parse_arg_id) { TEST(base_test, constexpr_parse_arg_id) {
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
static_assert(parse_arg_id("42:").res == arg_id_result::index, ""); static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
static_assert(parse_arg_id("42:").index == 42, ""); static_assert(parse_arg_id("42:").index == 42, "");
static_assert(parse_arg_id("foo:").res == arg_id_result::name, ""); static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
@ -504,19 +516,19 @@ template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
} }
TEST(base_test, constexpr_parse_format_specs) { TEST(base_test, constexpr_parse_format_specs) {
static_assert(parse_test_specs("<").align == fmt::align::left, ""); static_assert(parse_test_specs("<").align() == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill.get<char>() == '*', ""); static_assert(parse_test_specs("*^").fill_unit<char>() == '*', "");
static_assert(parse_test_specs("+").sign == fmt::sign::plus, ""); static_assert(parse_test_specs("+").sign() == fmt::sign::plus, "");
static_assert(parse_test_specs("-").sign == fmt::sign::minus, ""); static_assert(parse_test_specs("-").sign() == fmt::sign::none, "");
static_assert(parse_test_specs(" ").sign == fmt::sign::space, ""); static_assert(parse_test_specs(" ").sign() == fmt::sign::space, "");
static_assert(parse_test_specs("#").alt, ""); static_assert(parse_test_specs("#").alt(), "");
static_assert(parse_test_specs("0").align == fmt::align::numeric, ""); static_assert(parse_test_specs("0").align() == fmt::align::numeric, "");
static_assert(parse_test_specs("L").localized, ""); static_assert(parse_test_specs("L").localized(), "");
static_assert(parse_test_specs("42").width == 42, ""); static_assert(parse_test_specs("42").width == 42, "");
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, ""); static_assert(parse_test_specs("{42}").width_ref.index == 42, "");
static_assert(parse_test_specs(".42").precision == 42, ""); static_assert(parse_test_specs(".42").precision == 42, "");
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, ""); static_assert(parse_test_specs(".{42}").precision_ref.index == 42, "");
static_assert(parse_test_specs("f").type == fmt::presentation_type::fixed, static_assert(parse_test_specs("f").type() == fmt::presentation_type::fixed,
""); "");
} }
@ -539,9 +551,9 @@ struct test_format_string_handler {
bool error = false; bool error = false;
}; };
template <size_t N> constexpr bool parse_string(const char (&s)[N]) { template <size_t N> constexpr auto parse_string(const char (&s)[N]) -> bool {
auto h = test_format_string_handler(); auto h = test_format_string_handler();
fmt::detail::parse_format_string<true>(fmt::string_view(s, N - 1), h); fmt::detail::parse_format_string(fmt::string_view(s, N - 1), h);
return !h.error; return !h.error;
} }
@ -553,6 +565,7 @@ TEST(base_test, constexpr_parse_format_string) {
static_assert(parse_string("{foo}"), ""); static_assert(parse_string("{foo}"), "");
static_assert(parse_string("{:}"), ""); static_assert(parse_string("{:}"), "");
} }
#endif // FMT_USE_CONSTEXPR #endif // FMT_USE_CONSTEXPR
struct enabled_formatter {}; struct enabled_formatter {};
@ -584,15 +597,6 @@ template <> struct formatter<enabled_ptr_formatter*> {
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(base_test, has_formatter) {
using fmt::has_formatter;
using context = fmt::format_context;
static_assert(has_formatter<enabled_formatter, context>::value, "");
static_assert(!has_formatter<disabled_formatter, context>::value, "");
static_assert(!has_formatter<disabled_formatter_convertible, context>::value,
"");
}
struct const_formattable {}; struct const_formattable {};
struct nonconst_formattable {}; struct nonconst_formattable {};
@ -644,51 +648,48 @@ FMT_END_NAMESPACE
enum class unformattable_scoped_enum {}; enum class unformattable_scoped_enum {};
TEST(base_test, is_formattable) { TEST(base_test, is_formattable) {
static_assert(!fmt::is_formattable<wchar_t>::value, ""); EXPECT_FALSE(fmt::is_formattable<void>::value);
EXPECT_FALSE(fmt::is_formattable<wchar_t>::value);
#ifdef __cpp_char8_t #ifdef __cpp_char8_t
static_assert(!fmt::is_formattable<char8_t>::value, ""); EXPECT_FALSE(fmt::is_formattable<char8_t>::value);
#endif #endif
static_assert(!fmt::is_formattable<char16_t>::value, ""); EXPECT_FALSE(fmt::is_formattable<char16_t>::value);
static_assert(!fmt::is_formattable<char32_t>::value, ""); EXPECT_FALSE(fmt::is_formattable<char32_t>::value);
static_assert(!fmt::is_formattable<signed char*>::value, ""); EXPECT_FALSE(fmt::is_formattable<signed char*>::value);
static_assert(!fmt::is_formattable<unsigned char*>::value, ""); EXPECT_FALSE(fmt::is_formattable<unsigned char*>::value);
static_assert(!fmt::is_formattable<const signed char*>::value, ""); EXPECT_FALSE(fmt::is_formattable<const signed char*>::value);
static_assert(!fmt::is_formattable<const unsigned char*>::value, ""); EXPECT_FALSE(fmt::is_formattable<const unsigned char*>::value);
static_assert(!fmt::is_formattable<const wchar_t*>::value, ""); EXPECT_FALSE(fmt::is_formattable<const wchar_t*>::value);
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, ""); EXPECT_FALSE(fmt::is_formattable<const wchar_t[3]>::value);
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value, EXPECT_FALSE(fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value);
""); EXPECT_FALSE(fmt::is_formattable<enabled_ptr_formatter*>::value);
static_assert(fmt::is_formattable<enabled_formatter>::value, ""); EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, ""); EXPECT_FALSE(fmt::is_formattable<disabled_formatter_convertible>::value);
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
"");
static_assert(fmt::is_formattable<const_formattable&>::value, ""); EXPECT_TRUE(fmt::is_formattable<enabled_formatter>::value);
static_assert(fmt::is_formattable<const const_formattable&>::value, ""); EXPECT_TRUE(fmt::is_formattable<const_formattable&>::value);
EXPECT_TRUE(fmt::is_formattable<const const_formattable&>::value);
static_assert(fmt::is_formattable<nonconst_formattable&>::value, ""); EXPECT_TRUE(fmt::is_formattable<nonconst_formattable&>::value);
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 EXPECT_FALSE(fmt::is_formattable<const nonconst_formattable&>::value);
static_assert(!fmt::is_formattable<const nonconst_formattable&>::value, "");
#endif
static_assert(!fmt::is_formattable<convertible_to_pointer>::value, ""); EXPECT_FALSE(fmt::is_formattable<convertible_to_pointer>::value);
const auto f = convertible_to_pointer_formattable(); const auto f = convertible_to_pointer_formattable();
auto str = std::string(); auto str = std::string();
fmt::format_to(std::back_inserter(str), "{}", f); fmt::format_to(std::back_inserter(str), "{}", f);
EXPECT_EQ(str, "test"); EXPECT_EQ(str, "test");
static_assert(!fmt::is_formattable<void (*)()>::value, ""); EXPECT_FALSE(fmt::is_formattable<void (*)()>::value);
struct s; struct s;
static_assert(!fmt::is_formattable<int(s::*)>::value, ""); EXPECT_FALSE(fmt::is_formattable<int(s::*)>::value);
static_assert(!fmt::is_formattable<int (s::*)()>::value, ""); EXPECT_FALSE(fmt::is_formattable<int (s::*)()>::value);
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, ""); EXPECT_FALSE(fmt::is_formattable<unformattable_scoped_enum>::value);
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, ""); EXPECT_FALSE(fmt::is_formattable<unformattable_scoped_enum>::value);
} }
#if FMT_USE_CONCEPTS #ifdef __cpp_concepts
TEST(base_test, formattable) { TEST(base_test, formattable_concept) {
static_assert(fmt::formattable<char>); static_assert(fmt::formattable<char>);
static_assert(fmt::formattable<char&>); static_assert(fmt::formattable<char&>);
static_assert(fmt::formattable<char&&>); static_assert(fmt::formattable<char&&>);
@ -709,56 +710,48 @@ TEST(base_test, format_to) {
TEST(base_test, format_to_array) { TEST(base_test, format_to_array) {
char buffer[4]; char buffer[4];
auto result = fmt::format_to(buffer, "{}", 12345); auto result = fmt::format_to(buffer, "{}", 12345);
EXPECT_EQ(4, std::distance(&buffer[0], result.out)); EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated); EXPECT_TRUE(result.truncated);
EXPECT_EQ(buffer + 4, result.out); EXPECT_EQ(result.out, buffer + 4);
EXPECT_EQ("1234", fmt::string_view(buffer, 4)); EXPECT_EQ(fmt::string_view(buffer, 4), "1234");
char* out = nullptr; char* out = nullptr;
EXPECT_THROW(out = result, std::runtime_error); EXPECT_THROW(out = result, std::runtime_error);
(void)out; (void)out;
result = fmt::format_to(buffer, "{:s}", "foobar"); result = fmt::format_to(buffer, "{:s}", "foobar");
EXPECT_EQ(4, std::distance(&buffer[0], result.out)); EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated); EXPECT_TRUE(result.truncated);
EXPECT_EQ(buffer + 4, result.out); EXPECT_EQ(result.out, buffer + 4);
EXPECT_EQ("foob", fmt::string_view(buffer, 4)); EXPECT_EQ(fmt::string_view(buffer, 4), "foob");
buffer[0] = 'x'; buffer[0] = 'x';
buffer[1] = 'x'; buffer[1] = 'x';
buffer[2] = 'x'; buffer[2] = 'x';
buffer[3] = 'x'; buffer[3] = 'x';
result = fmt::format_to(buffer, "{}", 'A'); result = fmt::format_to(buffer, "{}", 'A');
EXPECT_EQ(1, std::distance(&buffer[0], result.out)); EXPECT_EQ(std::distance(&buffer[0], result.out), 1);
EXPECT_FALSE(result.truncated); EXPECT_FALSE(result.truncated);
EXPECT_EQ(buffer + 1, result.out); EXPECT_EQ(result.out, buffer + 1);
EXPECT_EQ("Axxx", fmt::string_view(buffer, 4)); EXPECT_EQ(fmt::string_view(buffer, 4), "Axxx");
result = fmt::format_to(buffer, "{}{} ", 'B', 'C'); result = fmt::format_to(buffer, "{}{} ", 'B', 'C');
EXPECT_EQ(3, std::distance(&buffer[0], result.out)); EXPECT_EQ(std::distance(&buffer[0], result.out), 3);
EXPECT_FALSE(result.truncated); EXPECT_FALSE(result.truncated);
EXPECT_EQ(buffer + 3, result.out); EXPECT_EQ(result.out, buffer + 3);
EXPECT_EQ("BC x", fmt::string_view(buffer, 4)); EXPECT_EQ(fmt::string_view(buffer, 4), "BC x");
result = fmt::format_to(buffer, "{}", "ABCDE"); result = fmt::format_to(buffer, "{}", "ABCDE");
EXPECT_EQ(4, std::distance(&buffer[0], result.out)); EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated); EXPECT_TRUE(result.truncated);
EXPECT_EQ("ABCD", fmt::string_view(buffer, 4)); EXPECT_EQ(fmt::string_view(buffer, 4), "ABCD");
result = fmt::format_to(buffer, "{}", std::string(1000, '*')); result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str());
EXPECT_EQ(4, std::distance(&buffer[0], result.out)); EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated); EXPECT_TRUE(result.truncated);
EXPECT_EQ("****", fmt::string_view(buffer, 4)); EXPECT_EQ(fmt::string_view(buffer, 4), "****");
} }
#ifdef __cpp_lib_byte
TEST(base_test, format_byte) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
EXPECT_EQ(s, "42");
}
#endif
// Test that check is not found by ADL. // Test that check is not found by ADL.
template <typename T> void check(T); template <typename T> void check(T);
TEST(base_test, adl_check) { TEST(base_test, adl_check) {
@ -776,19 +769,6 @@ TEST(base_test, no_implicit_conversion_to_string_view) {
fmt::is_formattable<implicitly_convertible_to_string_view>::value); fmt::is_formattable<implicitly_convertible_to_string_view>::value);
} }
#ifdef FMT_USE_STRING_VIEW
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(base_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
#endif
// std::is_constructible is broken in MSVC until version 2015.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
struct explicitly_convertible_to_string_view { struct explicitly_convertible_to_string_view {
explicit operator fmt::string_view() const { return "foo"; } explicit operator fmt::string_view() const { return "foo"; }
}; };
@ -800,7 +780,16 @@ TEST(base_test, format_explicitly_convertible_to_string_view) {
!fmt::is_formattable<explicitly_convertible_to_string_view>::value, ""); !fmt::is_formattable<explicitly_convertible_to_string_view>::value, "");
} }
# ifdef FMT_USE_STRING_VIEW #if FMT_CPLUSPLUS >= 201703L
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(base_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
struct explicitly_convertible_to_std_string_view { struct explicitly_convertible_to_std_string_view {
explicit operator std::string_view() const { return "foo"; } explicit operator std::string_view() const { return "foo"; }
}; };
@ -812,14 +801,12 @@ TEST(base_test, format_explicitly_convertible_to_std_string_view) {
!fmt::is_formattable<explicitly_convertible_to_std_string_view>::value, !fmt::is_formattable<explicitly_convertible_to_std_string_view>::value,
""); "");
} }
# endif #endif // FMT_CPLUSPLUS >= 201703L
#endif
TEST(base_test, has_const_formatter) { TEST(base_test, has_formatter) {
EXPECT_TRUE((fmt::detail::has_const_formatter<const_formattable, EXPECT_TRUE((fmt::detail::has_formatter<const const_formattable, char>()));
fmt::format_context>())); EXPECT_FALSE(
EXPECT_FALSE((fmt::detail::has_const_formatter<nonconst_formattable, (fmt::detail::has_formatter<const nonconst_formattable, char>()));
fmt::format_context>()));
} }
TEST(base_test, format_nonconst) { TEST(base_test, format_nonconst) {
@ -829,7 +816,7 @@ TEST(base_test, format_nonconst) {
} }
TEST(base_test, throw_in_buffer_dtor) { TEST(base_test, throw_in_buffer_dtor) {
enum { buffer_size = 256 }; constexpr int buffer_size = 256;
struct throwing_iterator { struct throwing_iterator {
int& count; int& count;
@ -851,7 +838,7 @@ TEST(base_test, throw_in_buffer_dtor) {
} }
} }
struct its_a_trap { struct convertible_to_any_type_with_member_x {
template <typename T> operator T() const { template <typename T> operator T() const {
auto v = T(); auto v = T();
v.x = 42; v.x = 42;
@ -860,12 +847,12 @@ struct its_a_trap {
}; };
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct formatter<its_a_trap> { template <> struct formatter<convertible_to_any_type_with_member_x> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
auto format(its_a_trap, format_context& ctx) const auto format(convertible_to_any_type_with_member_x, format_context& ctx) const
-> decltype(ctx.out()) const { -> decltype(ctx.out()) const {
auto out = ctx.out(); auto out = ctx.out();
*out++ = 'x'; *out++ = 'x';
@ -874,8 +861,51 @@ template <> struct formatter<its_a_trap> {
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(base_test, trappy_conversion) { TEST(base_test, promiscuous_conversions) {
auto s = std::string(); auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", its_a_trap()); fmt::format_to(std::back_inserter(s), "{}",
convertible_to_any_type_with_member_x());
EXPECT_EQ(s, "x"); EXPECT_EQ(s, "x");
} }
struct custom_container {
char data;
using value_type = char;
auto size() const -> size_t { return 0; }
void resize(size_t) {}
void push_back(char) {}
auto operator[](size_t) -> char& { return data; }
};
FMT_BEGIN_NAMESPACE
template <> struct is_contiguous<custom_container> : std::true_type {};
FMT_END_NAMESPACE
TEST(base_test, format_to_custom_container) {
auto c = custom_container();
fmt::format_to(std::back_inserter(c), "");
}
TEST(base_test, no_repeated_format_string_conversions) {
struct nondeterministic_format_string {
mutable int i = 0;
FMT_CONSTEXPR operator fmt::string_view() const {
return {"{}", i++ != 0 ? 2u : 0u};
}
};
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200
char buf[10];
fmt::format_to(buf, nondeterministic_format_string());
#endif
}
TEST(base_test, format_context_accessors) {
auto copy = [](fmt::appender app, const fmt::format_context& ctx) {
return fmt::format_context(app, ctx.args(), ctx.locale());
};
fmt::detail::ignore_unused(copy);
}

View File

@ -15,11 +15,9 @@
#include "util.h" // get_locale #include "util.h" // get_locale
using fmt::runtime; using fmt::runtime;
using fmt::sys_time;
using testing::Contains; using testing::Contains;
template <typename Duration>
using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
#if defined(__MINGW32__) && !defined(_UCRT) #if defined(__MINGW32__) && !defined(_UCRT)
// Only C89 conversion specifiers when using MSVCRT instead of UCRT // Only C89 conversion specifiers when using MSVCRT instead of UCRT
# define FMT_HAS_C99_STRFTIME 0 # define FMT_HAS_C99_STRFTIME 0
@ -57,8 +55,8 @@ auto make_second(int s) -> std::tm {
return time; return time;
} }
std::string system_strftime(const std::string& format, const std::tm* timeptr, auto system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) { std::locale* locptr = nullptr) -> std::string {
auto loc = locptr ? *locptr : std::locale::classic(); auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<char>>(loc); auto& facet = std::use_facet<std::time_put<char>>(loc);
std::ostringstream os; std::ostringstream os;
@ -75,8 +73,8 @@ std::string system_strftime(const std::string& format, const std::tm* timeptr,
#endif #endif
} }
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min, FMT_CONSTEXPR auto make_tm(int year, int mon, int mday, int hour, int min,
int sec) { int sec) -> std::tm {
auto tm = std::tm(); auto tm = std::tm();
tm.tm_sec = sec; tm.tm_sec = sec;
tm.tm_min = min; tm.tm_min = min;
@ -240,196 +238,145 @@ TEST(chrono_test, format_to_empty_container) {
EXPECT_EQ(s, "42"); EXPECT_EQ(s, "42");
} }
TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday &&
lhs.tm_isdst == rhs.tm_isdst;
}
TEST(chrono_test, gmtime) { TEST(chrono_test, gmtime) {
auto t = std::time(nullptr); auto t = std::time(nullptr);
auto tm = *std::gmtime(&t); auto expected = *std::gmtime(&t);
EXPECT_TRUE(equal(tm, fmt::gmtime(t))); auto actual = fmt::gmtime(t);
EXPECT_EQ(actual.tm_sec, expected.tm_sec);
EXPECT_EQ(actual.tm_min, expected.tm_min);
EXPECT_EQ(actual.tm_hour, expected.tm_hour);
EXPECT_EQ(actual.tm_mday, expected.tm_mday);
EXPECT_EQ(actual.tm_mon, expected.tm_mon);
EXPECT_EQ(actual.tm_year, expected.tm_year);
EXPECT_EQ(actual.tm_wday, expected.tm_wday);
EXPECT_EQ(actual.tm_yday, expected.tm_yday);
EXPECT_EQ(actual.tm_isdst, expected.tm_isdst);
} }
template <typename TimePoint> template <typename Time> void test_time(Time time) {
auto strftime_full_utc(TimePoint tp) -> std::string { EXPECT_EQ(fmt::format("{}", time), "1979-03-12 12:00:00");
auto t = std::chrono::system_clock::to_time_t(tp); EXPECT_EQ(fmt::format("{:}", time), "1979-03-12 12:00:00");
auto tm = *std::gmtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm); EXPECT_EQ(fmt::format("{:%%}", time), "%");
EXPECT_EQ(fmt::format("{:%n}", time), "\n");
EXPECT_EQ(fmt::format("{:%t}", time), "\t");
EXPECT_EQ(fmt::format("{:%Y}", time), "1979");
EXPECT_EQ(fmt::format("{:%EY}", time), "1979");
EXPECT_EQ(fmt::format("{:%y}", time), "79");
EXPECT_EQ(fmt::format("{:%Oy}", time), "79");
EXPECT_EQ(fmt::format("{:%Ey}", time), "79");
EXPECT_EQ(fmt::format("{:%C}", time), "19");
EXPECT_EQ(fmt::format("{:%EC}", time), "19");
EXPECT_EQ(fmt::format("{:%G}", time), "1979");
EXPECT_EQ(fmt::format("{:%g}", time), "79");
EXPECT_EQ(fmt::format("{:%b}", time), "Mar");
EXPECT_EQ(fmt::format("{:%h}", time), "Mar");
EXPECT_EQ(fmt::format("{:%B}", time), "March");
EXPECT_EQ(fmt::format("{:%m}", time), "03");
EXPECT_EQ(fmt::format("{:%Om}", time), "03");
EXPECT_EQ(fmt::format("{:%U}", time), "10");
EXPECT_EQ(fmt::format("{:%OU}", time), "10");
EXPECT_EQ(fmt::format("{:%W}", time), "11");
EXPECT_EQ(fmt::format("{:%OW}", time), "11");
EXPECT_EQ(fmt::format("{:%V}", time), "11");
EXPECT_EQ(fmt::format("{:%OV}", time), "11");
EXPECT_EQ(fmt::format("{:%j}", time), "071");
EXPECT_EQ(fmt::format("{:%d}", time), "12");
EXPECT_EQ(fmt::format("{:%Od}", time), "12");
EXPECT_EQ(fmt::format("{:%e}", time), "12");
EXPECT_EQ(fmt::format("{:%Oe}", time), "12");
EXPECT_EQ(fmt::format("{:%a}", time), "Mon");
EXPECT_EQ(fmt::format("{:%A}", time), "Monday");
EXPECT_EQ(fmt::format("{:%w}", time), "1");
EXPECT_EQ(fmt::format("{:%Ow}", time), "1");
EXPECT_EQ(fmt::format("{:%u}", time), "1");
EXPECT_EQ(fmt::format("{:%Ou}", time), "1");
EXPECT_EQ(fmt::format("{:%H}", time), "12");
EXPECT_EQ(fmt::format("{:%OH}", time), "12");
EXPECT_EQ(fmt::format("{:%I}", time), "12");
EXPECT_EQ(fmt::format("{:%OI}", time), "12");
EXPECT_EQ(fmt::format("{:%M}", time), "00");
EXPECT_EQ(fmt::format("{:%OM}", time), "00");
EXPECT_EQ(fmt::format("{:%S}", time), "00");
EXPECT_EQ(fmt::format("{:%OS}", time), "00");
EXPECT_EQ(fmt::format("{:%x}", time), "03/12/79");
EXPECT_EQ(fmt::format("{:%Ex}", time), "03/12/79");
EXPECT_EQ(fmt::format("{:%X}", time), "12:00:00");
EXPECT_EQ(fmt::format("{:%EX}", time), "12:00:00");
EXPECT_EQ(fmt::format("{:%D}", time), "03/12/79");
EXPECT_EQ(fmt::format("{:%F}", time), "1979-03-12");
EXPECT_EQ(fmt::format("{:%R}", time), "12:00");
EXPECT_EQ(fmt::format("{:%T}", time), "12:00:00");
EXPECT_EQ(fmt::format("{:%p}", time), "PM");
EXPECT_EQ(fmt::format("{:%c}", time), "Mon Mar 12 12:00:00 1979");
EXPECT_EQ(fmt::format("{:%Ec}", time), "Mon Mar 12 12:00:00 1979");
EXPECT_EQ(fmt::format("{:%r}", time), "12:00:00 PM");
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", time), "1979-03-12 12:00:00");
} }
TEST(chrono_test, system_clock_time_point) { TEST(chrono_test, sys_time) {
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>( auto time =
std::chrono::system_clock::now()); fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); test_time(time);
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1)); EXPECT_EQ(fmt::format("{:%z}", time), "+0000");
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1)); EXPECT_EQ(fmt::format("{:%Ez}", time), "+00:00");
EXPECT_EQ(fmt::format("{:%Oz}", time), "+00:00");
EXPECT_EQ(fmt::format("{:%Z}", time), "UTC");
}
auto t2 = sys_time<std::chrono::seconds>(std::chrono::seconds(42)); TEST(chrono_test, local_time) {
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); auto time =
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(290088000));
test_time(time);
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
fmt::format_error, "no timezone");
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
fmt::format_error, "no timezone");
}
std::vector<std::string> spec_list = { template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", auto set_tm_gmtoff(T& time, long offset) -> bool {
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U", time.tm_gmtoff = offset;
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", return true;
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH", }
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X", template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
"%EX", "%D", "%F", "%R", "%T", "%p"}; auto set_tm_gmtoff(T&, long) -> bool {
#ifndef _WIN32 return false;
// Disabled on Windows because these formats are not consistent among }
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d",
"%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"};
#endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) { TEST(chrono_test, tm) {
auto t = std::chrono::system_clock::to_time_t(t1); auto time = fmt::gmtime(290088000);
auto tm = *std::gmtime(&t); test_time(time);
if (set_tm_gmtoff(time, -28800)) {
auto sys_output = system_strftime(spec, &tm); EXPECT_EQ(fmt::format(fmt::runtime("{:%z}"), time), "-0800");
EXPECT_EQ(fmt::format(fmt::runtime("{:%Ez}"), time), "-08:00");
auto fmt_spec = fmt::format("{{:{}}}", spec); EXPECT_EQ(fmt::format(fmt::runtime("{:%Oz}"), time), "-08:00");
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1)); } else {
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
fmt::format_error, "no timezone");
} }
char tz[] = "EET";
// Timezone formatters tests makes sense for localtime. if (fmt::detail::set_tm_zone(time, tz)) {
#if FMT_HAS_C99_STRFTIME EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET");
spec_list = {"%z", "%Z"}; } else {
#else EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
spec_list = {"%Z"}; fmt::format_error, "no timezone");
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
if (spec == "%z") {
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
}
}
// Separate tests for UTC, since std::time_put can use local time and ignoring
// the timezone in std::tm (if it presents on platform).
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
std::vector<std::string> tz_names = {"GMT", "UTC"};
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
}
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
EXPECT_EQ(fmt::format("{:%z}", t1), "+0000");
EXPECT_EQ(fmt::format("{:%z}", tm), "+0000");
EXPECT_EQ(fmt::format("{:%Ez}", t1), "+00:00");
EXPECT_EQ(fmt::format("{:%Ez}", tm), "+00:00");
EXPECT_EQ(fmt::format("{:%Oz}", t1), "+00:00");
EXPECT_EQ(fmt::format("{:%Oz}", tm), "+00:00");
} }
} }
#if FMT_USE_LOCAL_TIME TEST(chrono_test, daylight_savings_time_end) {
// 2024-10-27 03:05 as the number of seconds since epoch in Europe/Kyiv time.
TEST(chrono_test, localtime) { // It is slightly after the DST end and passing it to to_sys will result in
auto t = std::time(nullptr); // an ambiguous time error:
auto tm = *std::localtime(&t); // 2024-10-27 03:05:00 is ambiguous. It could be
EXPECT_TRUE(equal(tm, fmt::localtime(t))); // 2024-10-27 03:05:00 EEST == 2024-10-27 00:05:00 UTC or
// 2024-10-27 03:05:00 EET == 2024-10-27 01:05:00 UTC
auto t =
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(1729998300));
EXPECT_EQ(fmt::format("{}", t), "2024-10-27 03:05:00");
} }
template <typename Duration>
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(tp));
auto tm = *std::localtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
}
TEST(chrono_test, local_system_clock_time_point) {
# ifdef _WIN32
return; // Not supported on Windows.
# endif
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
using time_point = std::chrono::local_time<std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(86400 + 42));
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
# ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
# elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
# endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
spec_list.cend()) {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime("%z", &tm);
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
}
}
#endif // FMT_USE_LOCAL_TIME
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
TEST(chrono_test, format_default) { TEST(chrono_test, format_default) {
EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s"); EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)), EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)),
@ -539,6 +486,7 @@ TEST(chrono_test, format_specs) {
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12"); EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12");
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04"); EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04");
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02"); EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02");
EXPECT_EQ(fmt::format("{:%j}", days(12)), "12");
EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345"); EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345");
EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345"); EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)), EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)),
@ -605,12 +553,12 @@ auto format_tm(const std::tm& time, fmt::string_view spec,
TEST(chrono_test, locale) { TEST(chrono_test, locale) {
auto loc = get_locale("ja_JP.utf8"); auto loc = get_locale("ja_JP.utf8");
if (loc == std::locale::classic()) return; if (loc == std::locale::classic()) return;
# define EXPECT_TIME(spec, time, duration) \ #define EXPECT_TIME(spec, time, duration) \
{ \ { \
auto jp_loc = std::locale("ja_JP.utf8"); \ auto jp_loc = std::locale("ja_JP.utf8"); \
EXPECT_EQ(format_tm(time, spec, jp_loc), \ EXPECT_EQ(format_tm(time, spec, jp_loc), \
fmt::format(jp_loc, "{:L" spec "}", duration)); \ fmt::format(jp_loc, "{:L" spec "}", duration)); \
} }
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14)); EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14)); EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42)); EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
@ -753,7 +701,7 @@ TEST(chrono_test, weekday) {
std::locale::global(loc); std::locale::global(loc);
auto sat = fmt::weekday(6); auto sat = fmt::weekday(6);
auto tm = std::tm(); auto tm = std::tm();
tm.tm_wday = static_cast<int>(sat.c_encoding()); tm.tm_wday = static_cast<int>(sat.c_encoding());
@ -763,10 +711,10 @@ TEST(chrono_test, weekday) {
EXPECT_EQ(fmt::format("{:%a}", tm), "Sat"); EXPECT_EQ(fmt::format("{:%a}", tm), "Sat");
if (loc != std::locale::classic()) { if (loc != std::locale::classic()) {
auto saturdays = std::vector<std::string>{"sáb", "sá."}; auto saturdays = std::vector<std::string>{"sáb", "sá.", "sáb."};
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat))); EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat))); EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm))); EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm)));
} }
} }
@ -795,20 +743,14 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
"01.234000"); "01.234000");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}), EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
"-01.234000"); "-01.234000");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}), EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}), "12.34");
"12.34"); EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}), "12.37");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}),
"12.37");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{-12375}), EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{-12375}),
"-12.37"); "-12.37");
EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}), EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}), "12");
"12"); EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}), "39.99");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}), EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}), "01.00");
"39.99"); EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}), "00.001");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}),
"01.00");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}),
"00.001");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000"); EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000"); EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123"); EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
@ -822,11 +764,11 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000"); EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
} }
using nanoseconds_dbl = std::chrono::duration<double, std::nano>; using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(-123456789)), "-00.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789"); EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(9123456789)), "09.123456789");
// Verify that only the seconds part is extracted and printed. // Verify that only the seconds part is extracted and printed.
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789"); EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123456789)), "39.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000"); EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123000000)), "39.123000000");
{ {
// Now the hour is printed, and we also test if negative doubles work. // Now the hour is printed, and we also test if negative doubles work.
auto dur = nanoseconds_dbl{-99123456789}; auto dur = nanoseconds_dbl{-99123456789};
@ -837,7 +779,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
} }
// Check that durations with precision greater than std::chrono::seconds have // Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part. // fixed precision, and print zeros even if there is no fractional part.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds(7000000)),
"07.000000"); "07.000000");
EXPECT_EQ(fmt::format("{:%S}", EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 3>>(1)), std::chrono::duration<long long, std::ratio<1, 3>>(1)),
@ -857,14 +799,12 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
"-05:27.68"); "-05:27.68");
// Check that floating point seconds with ratio<1,1> are printed. // Check that floating point seconds with ratio<1,1> are printed.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}), EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1.5)),
"01.500000"); "01.500000");
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}), EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-61.25)),
"-01:01.250000"); "-01:01.250000");
} }
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
// Disable the utc_clock test for windows, as the icu.dll used for tzdb // Disable the utc_clock test for windows, as the icu.dll used for tzdb
// (time zone database) is not shipped with many windows versions. // (time zone database) is not shipped with many windows versions.
#if FMT_USE_UTC_TIME && !defined(_WIN32) #if FMT_USE_UTC_TIME && !defined(_WIN32)
@ -937,19 +877,11 @@ TEST(chrono_test, timestamp_sub_seconds) {
auto t8 = auto t8 =
sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789)); sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789));
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789"); EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
EXPECT_EQ(fmt::format("{:%T}", t8), "00:00:00.123456789");
auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>( auto t9 =
std::chrono::system_clock::now());
auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
fmt::format("{:%Y-%m-%d %T}", t9));
auto t10 =
sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000)); sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000"); EXPECT_EQ(fmt::format("{:%S}", t9), "02.000");
auto epoch = sys_time<std::chrono::milliseconds>(); auto epoch = sys_time<std::chrono::milliseconds>();
auto d = std::chrono::milliseconds(250); auto d = std::chrono::milliseconds(250);
@ -1011,6 +943,10 @@ TEST(chrono_test, glibc_extensions) {
EXPECT_EQ(fmt::format("{:%U,%W,%V}", t), "02,01,01"); EXPECT_EQ(fmt::format("{:%U,%W,%V}", t), "02,01,01");
EXPECT_EQ(fmt::format("{:%_U,%_W,%_V}", t), " 2, 1, 1"); EXPECT_EQ(fmt::format("{:%_U,%_W,%_V}", t), " 2, 1, 1");
EXPECT_EQ(fmt::format("{:%-U,%-W,%-V}", t), "2,1,1"); EXPECT_EQ(fmt::format("{:%-U,%-W,%-V}", t), "2,1,1");
EXPECT_EQ(fmt::format("{:%j}", t), "008");
EXPECT_EQ(fmt::format("{:%_j}", t), " 8");
EXPECT_EQ(fmt::format("{:%-j}", t), "8");
} }
{ {
@ -1022,6 +958,30 @@ TEST(chrono_test, glibc_extensions) {
EXPECT_EQ(fmt::format("{:%e}", t), " 7"); EXPECT_EQ(fmt::format("{:%e}", t), " 7");
} }
{
auto t = std::tm();
t.tm_year = 7 - 1900;
EXPECT_EQ(fmt::format("{:%Y}", t), "0007");
EXPECT_EQ(fmt::format("{:%_Y}", t), " 7");
EXPECT_EQ(fmt::format("{:%-Y}", t), "7");
}
{
auto t = std::tm();
t.tm_year = -5 - 1900;
EXPECT_EQ(fmt::format("{:%Y}", t), "-005");
EXPECT_EQ(fmt::format("{:%_Y}", t), " -5");
EXPECT_EQ(fmt::format("{:%-Y}", t), "-5");
}
{
auto t = std::tm();
t.tm_mon = 7 - 1;
EXPECT_EQ(fmt::format("{:%m}", t), "07");
EXPECT_EQ(fmt::format("{:%_m}", t), " 7");
EXPECT_EQ(fmt::format("{:%-m}", t), "7");
}
} }
TEST(chrono_test, out_of_range) { TEST(chrono_test, out_of_range) {
@ -1032,7 +992,7 @@ TEST(chrono_test, out_of_range) {
TEST(chrono_test, year_month_day) { TEST(chrono_test, year_month_day) {
auto loc = get_locale("es_ES.UTF-8"); auto loc = get_locale("es_ES.UTF-8");
std::locale::global(loc); std::locale::global(loc);
auto year = fmt::year(2024); auto year = fmt::year(2024);
auto month = fmt::month(1); auto month = fmt::month(1);
auto day = fmt::day(1); auto day = fmt::day(1);
@ -1058,6 +1018,6 @@ TEST(chrono_test, year_month_day) {
if (loc != std::locale::classic()) { if (loc != std::locale::classic()) {
auto months = std::vector<std::string>{"ene.", "ene"}; auto months = std::vector<std::string>{"ene.", "ene"};
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month))); EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month)));
EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month))); EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month)));
} }
} }

View File

@ -9,11 +9,68 @@
#include <iterator> // std::back_inserter #include <iterator> // std::back_inserter
#include "gtest-extra.h" // EXPECT_WRITE #include "gtest-extra.h" // EXPECT_WRITE, EXPECT_THROW_MSG
TEST(color_test, text_style) {
EXPECT_FALSE(fmt::text_style().has_foreground());
EXPECT_FALSE(fmt::text_style().has_background());
EXPECT_FALSE(fmt::text_style().has_emphasis());
EXPECT_TRUE(fg(fmt::rgb(0)).has_foreground());
EXPECT_FALSE(fg(fmt::rgb(0)).has_background());
EXPECT_FALSE(fg(fmt::rgb(0)).has_emphasis());
EXPECT_TRUE(bg(fmt::rgb(0)).has_background());
EXPECT_FALSE(bg(fmt::rgb(0)).has_foreground());
EXPECT_FALSE(bg(fmt::rgb(0)).has_emphasis());
EXPECT_TRUE(
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_foreground());
EXPECT_TRUE(
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_background());
EXPECT_FALSE(
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_emphasis());
EXPECT_EQ(fg(fmt::rgb(0x000000)) | fg(fmt::rgb(0x000000)),
fg(fmt::rgb(0x000000)));
EXPECT_EQ(fg(fmt::rgb(0x00000F)) | fg(fmt::rgb(0x00000F)),
fg(fmt::rgb(0x00000F)));
EXPECT_EQ(fg(fmt::rgb(0xC0F000)) | fg(fmt::rgb(0x000FEE)),
fg(fmt::rgb(0xC0FFEE)));
EXPECT_THROW_MSG(
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::white),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::white),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(fg(fmt::terminal_color::black) | fg(fmt::color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(bg(fmt::terminal_color::black) | bg(fmt::color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_NO_THROW(fg(fmt::terminal_color::white) |
bg(fmt::terminal_color::white));
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | bg(fmt::rgb(0xFFFFFF)));
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | fmt::text_style());
EXPECT_NO_THROW(bg(fmt::terminal_color::white) | fmt::text_style());
}
TEST(color_test, format) { TEST(color_test, format) {
EXPECT_EQ(fmt::format(fmt::text_style(), "no style"), "no style");
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 0, 0)) | fg(fmt::rgb(0, 20, 30)),
"rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_EQ(
fmt::format(fg(fmt::rgb(0, 0, 0)) | fg(fmt::rgb(0, 0, 0)), "rgb(0,0,0)"),
"\x1b[38;2;000;000;000mrgb(0,0,0)\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"), EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m"); "\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_EQ( EXPECT_EQ(
@ -56,6 +113,15 @@ TEST(color_test, format) {
EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) | EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) |
fmt::emphasis::underline)), fmt::emphasis::underline)),
"\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m"); "\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m");
EXPECT_EQ(
fmt::format(
"{}", fmt::styled(
"all", fmt::emphasis::bold | fmt::emphasis::faint |
fmt::emphasis::italic |
fmt::emphasis::underline | fmt::emphasis::blink |
fmt::emphasis::reverse | fmt::emphasis::conceal |
fmt::emphasis::strikethrough)),
"\x1b[1;2;3;4;5;7;8;9mall\x1b[0m");
} }
TEST(color_test, format_to) { TEST(color_test, format_to) {

View File

@ -115,8 +115,7 @@ function (run_tests)
endif () endif ()
endfunction () endfunction ()
# Check if the source file skeleton compiles.
# check if the source file skeleton compiles
expect_compile(check "") expect_compile(check "")
expect_compile(check-error "compilation_error" ERROR) expect_compile(check-error "compilation_error" ERROR)
@ -166,31 +165,6 @@ expect_compile(format-lots-of-arguments-with-function "
fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f); fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f);
" ERROR) " ERROR)
# Check if user-defined literals are available
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
check_cxx_source_compiles("
void operator\"\" _udl(long double);
int main() {}"
SUPPORTS_USER_DEFINED_LITERALS)
set(CMAKE_REQUIRED_FLAGS )
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
endif ()
# Make sure that compiler features detected in the header
# match the features detected in CMake.
if (SUPPORTS_USER_DEFINED_LITERALS)
set(supports_udl 1)
else ()
set(supports_udl 0)
endif ()
expect_compile(udl-check "
#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
# error
#endif
")
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
# Compile-time argument type check # Compile-time argument type check
expect_compile(format-string-number-spec " expect_compile(format-string-number-spec "

View File

@ -1,61 +0,0 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/compile.h"
#include "gmock/gmock.h"
#if FMT_USE_CONSTEVAL
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif // FMT_USE_CONSTEVAL

View File

@ -7,21 +7,16 @@
#include "fmt/compile.h" #include "fmt/compile.h"
#include <iterator>
#include <list>
#include <type_traits> #include <type_traits>
#include <vector>
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "gtest-extra.h" #include "gtest-extra.h"
TEST(iterator_test, counting_iterator) {
auto it = fmt::detail::counting_iterator();
auto prev = it++;
EXPECT_EQ(prev.count(), 0);
EXPECT_EQ(it.count(), 1);
EXPECT_EQ((it + 41).count(), 42);
}
TEST(compile_test, compile_fallback) { TEST(compile_test, compile_fallback) {
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
// not available. // not available.
@ -95,9 +90,6 @@ TEST(compile_test, format_escape) {
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc")); EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
} }
TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) { TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@ -129,7 +121,6 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo", "true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f, fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable())); "foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
} }
TEST(compile_test, named) { TEST(compile_test, named) {
@ -138,10 +129,6 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled), static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>); fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar", EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"), fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar"))); fmt::arg("a1", "bar")));
@ -206,7 +193,22 @@ TEST(compile_test, format_to_n) {
EXPECT_STREQ("2a", buffer); EXPECT_STREQ("2a", buffer);
} }
# ifdef __cpp_lib_bit_cast TEST(compile_test, output_iterators) {
std::list<char> out;
fmt::format_to(std::back_inserter(out), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", std::string(out.begin(), out.end()));
std::stringstream s;
fmt::format_to(std::ostream_iterator<char>(s), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", s.str());
std::stringstream s2;
fmt::format_to(std::ostreambuf_iterator<char>(s2), FMT_COMPILE("{}.{:06d}"),
42, 43);
EXPECT_EQ("42.000043", s2.str());
}
# if FMT_USE_CONSTEVAL && (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940)
TEST(compile_test, constexpr_formatted_size) { TEST(compile_test, constexpr_formatted_size) {
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42); FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
EXPECT_EQ(size, 2); EXPECT_EQ(size, 2);
@ -226,6 +228,12 @@ TEST(compile_test, constexpr_formatted_size) {
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc"); fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
EXPECT_EQ(str_size, 3); EXPECT_EQ(str_size, 3);
} }
TEST(compile_test, static_format) {
constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
EXPECT_STREQ(result.c_str(), "42");
EXPECT_EQ(result.str(), "42");
}
# endif # endif
TEST(compile_test, text_and_arg) { TEST(compile_test, text_and_arg) {
@ -237,10 +245,14 @@ TEST(compile_test, unknown_format_fallback) {
EXPECT_EQ(" 42 ", EXPECT_EQ(" 42 ",
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42))); fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
std::vector<char> v; std::vector<char> v1;
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"), fmt::format_to(std::back_inserter(v1), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", fmt::string_view(v1.data(), v1.size()));
std::vector<char> v2;
fmt::format_to(std::back_inserter(v2), FMT_COMPILE("{name:^4}"),
fmt::arg("name", 42)); fmt::arg("name", 42));
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size())); EXPECT_EQ(" 42 ", fmt::string_view(v2.data(), v2.size()));
char buffer[4]; char buffer[4];
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"), auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
@ -273,11 +285,23 @@ TEST(compile_test, to_string_and_formatter) {
fmt::format(FMT_COMPILE("{}"), to_stringable()); fmt::format(FMT_COMPILE("{}"), to_stringable());
} }
struct std_context_test {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<std_context_test> : formatter<int> {
auto format(std_context_test, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(compile_test, print) { TEST(compile_test, print) {
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"), EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!"); "Don't panic!");
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"), EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!"); "Don't panic!");
fmt::print(FMT_COMPILE("{}"), std_context_test());
} }
#endif #endif
@ -286,7 +310,17 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals; using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf)); EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42)); EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42)); }
#endif
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename S> auto check_is_compiled_string(const S&) -> bool {
return fmt::is_compiled_string<S>::value;
}
TEST(compile_test, is_compiled_string) {
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("asdf")));
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("{}")));
} }
#endif #endif
@ -301,7 +335,7 @@ TEST(compile_test, compile_format_string_literal) {
(FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930)) && \ (FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930)) && \
defined(__cpp_lib_is_constant_evaluated) defined(__cpp_lib_is_constant_evaluated)
template <size_t max_string_length, typename Char = char> struct test_string { template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept { template <typename T> constexpr auto operator==(const T& rhs) const -> bool {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0; return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
} }
Char buffer[max_string_length]{}; Char buffer[max_string_length]{};
@ -342,6 +376,7 @@ TEST(compile_time_formatting_test, integer) {
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a)); EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42)); EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42l));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll)); EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull)); EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
@ -382,4 +417,53 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) { TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42)); EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
} }
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif #endif
#if FMT_USE_CONSTEXPR_STRING
TEST(compile_test, constexpr_string_format) {
constexpr auto result = []() {
return fmt::format(FMT_COMPILE("{}"), 42) == "42";
}();
EXPECT_TRUE(result);
// Test with a larger string to avoid small string optimization.
constexpr auto big = []() {
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
}();
EXPECT_TRUE(big);
}
#endif // FMT_USE_CONSTEXPR_STRING

View File

@ -283,7 +283,7 @@ struct double_double {
double a; double a;
double b; double b;
explicit constexpr double_double(double a_val = 0, double b_val = 0) constexpr explicit double_double(double a_val = 0, double b_val = 0)
: a(a_val), b(b_val) {} : a(a_val), b(b_val) {}
operator double() const { return a + b; } operator double() const { return a + b; }
@ -292,14 +292,14 @@ struct double_double {
auto format_as(double_double d) -> double { return d; } auto format_as(double_double d) -> double { return d; }
bool operator>=(const double_double& lhs, const double_double& rhs) { auto operator>=(const double_double& lhs, const double_double& rhs) -> bool {
return lhs.a + lhs.b >= rhs.a + rhs.b; return lhs.a + lhs.b >= rhs.a + rhs.b;
} }
struct slow_float { struct slow_float {
float value; float value;
explicit constexpr slow_float(float val = 0) : value(val) {} constexpr explicit slow_float(float val = 0) : value(val) {}
operator float() const { return value; } operator float() const { return value; }
auto operator-() const -> slow_float { return slow_float(-value); } auto operator-() const -> slow_float { return slow_float(-value); }
}; };
@ -307,19 +307,20 @@ struct slow_float {
auto format_as(slow_float f) -> float { return f; } auto format_as(slow_float f) -> float { return f; }
namespace std { namespace std {
template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct numeric_limits<double_double> { template <> struct numeric_limits<double_double> {
// is_iec559 is true for double-double in libstdc++. // is_iec559 is true for double-double in libstdc++.
static constexpr bool is_iec559 = true; static constexpr bool is_iec559 = true;
static constexpr int digits = 106; static constexpr int digits = 106;
static constexpr int digits10 = 33;
}; };
template <> struct is_floating_point<slow_float> : std::true_type {};
template <> struct numeric_limits<slow_float> : numeric_limits<float> {}; template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
} // namespace std } // namespace std
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct is_floating_point<slow_float> : std::true_type {};
template <> struct is_fast_float<slow_float> : std::false_type {}; template <> struct is_fast_float<slow_float> : std::false_type {};
namespace dragonbox { namespace dragonbox {
template <> struct float_info<slow_float> { template <> struct float_info<slow_float> {
@ -341,7 +342,7 @@ TEST(format_impl_test, write_dragon_even) {
auto s = std::string(); auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {}); fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
// Specializing is_floating_point is broken in MSVC. // Specializing is_floating_point is broken in MSVC.
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450"); if (!FMT_MSC_VERSION) EXPECT_EQ(s, "3.355445e+07");
} }
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
@ -355,11 +356,11 @@ TEST(format_impl_test, write_console_signature) {
// A public domain branchless UTF-8 decoder by Christopher Wellons: // A public domain branchless UTF-8 decoder by Christopher Wellons:
// https://github.com/skeeto/branchless-utf8 // https://github.com/skeeto/branchless-utf8
constexpr bool unicode_is_surrogate(uint32_t c) { constexpr auto unicode_is_surrogate(uint32_t c) -> bool {
return c >= 0xD800U && c <= 0xDFFFU; return c >= 0xD800U && c <= 0xDFFFU;
} }
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) { FMT_CONSTEXPR auto utf8_encode(char* s, uint32_t c) -> char* {
if (c >= (1UL << 16)) { if (c >= (1UL << 16)) {
s[0] = static_cast<char>(0xf0 | (c >> 18)); s[0] = static_cast<char>(0xf0 | (c >> 18));
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f)); s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));

View File

@ -26,11 +26,17 @@
#include <string> // std::string #include <string> // std::string
#include <thread> // std::thread #include <thread> // std::thread
#include <type_traits> // std::is_default_constructible #include <type_traits> // std::is_default_constructible
#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<version>)
# include <version>
#endif
#include <limits.h>
#include <limits>
#include "gtest-extra.h" #include "gtest-extra.h"
#include "mock-allocator.h" #include "mock-allocator.h"
#include "util.h" #include "util.h"
using fmt::basic_memory_buffer; using fmt::basic_memory_buffer;
using fmt::format_error; using fmt::format_error;
using fmt::memory_buffer; using fmt::memory_buffer;
@ -42,6 +48,10 @@ using fmt::detail::uint128_fallback;
using testing::Return; using testing::Return;
using testing::StrictMock; using testing::StrictMock;
#ifdef __cpp_lib_concepts
static_assert(std::output_iterator<fmt::appender, char>);
#endif
enum { buffer_size = 256 }; enum { buffer_size = 256 };
TEST(uint128_test, ctor) { TEST(uint128_test, ctor) {
@ -196,10 +206,6 @@ TEST(util_test, parse_nonnegative_int) {
EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1); EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
} }
TEST(format_impl_test, compute_width) {
EXPECT_EQ(fmt::detail::compute_width("вожык"), 5);
}
TEST(util_test, utf8_to_utf16) { TEST(util_test, utf8_to_utf16) {
auto u = fmt::detail::utf8_to_utf16("лошадка"); auto u = fmt::detail::utf8_to_utf16("лошадка");
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str()); EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
@ -309,18 +315,17 @@ TEST(memory_buffer_test, move_ctor_inline_buffer) {
std::allocator<char>* alloc = buffer.get_allocator().get(); std::allocator<char>* alloc = buffer.get_allocator().get();
basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer)); basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
// Move shouldn't destroy the inline content of the first buffer. // Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(str, std::string(&buffer[0], buffer.size())); EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size())); EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
EXPECT_EQ(5u, buffer2.capacity()); EXPECT_EQ(buffer2.capacity(), 5u);
// Move should transfer allocator. // Move should transfer allocator.
EXPECT_EQ(nullptr, buffer.get_allocator().get()); EXPECT_EQ(buffer.get_allocator().get(), nullptr);
EXPECT_EQ(alloc, buffer2.get_allocator().get()); EXPECT_EQ(buffer2.get_allocator().get(), alloc);
}; };
auto alloc = std::allocator<char>(); auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc))); basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
const char test[] = "test"; buffer.append(string_view("test"));
buffer.append(string_view(test, 4));
check_move_buffer("test", buffer); check_move_buffer("test", buffer);
// Adding one more character fills the inline buffer, but doesn't cause // Adding one more character fills the inline buffer, but doesn't cause
// dynamic allocation. // dynamic allocation.
@ -345,14 +350,63 @@ TEST(memory_buffer_test, move_ctor_dynamic_buffer) {
EXPECT_GT(buffer2.capacity(), 4u); EXPECT_GT(buffer2.capacity(), 4u);
} }
using std_allocator_noprop = allocator_ref<std::allocator<char>, false>;
TEST(memory_buffer_test, move_ctor_inline_buffer_non_propagating) {
auto check_move_buffer =
[](const char* str,
basic_memory_buffer<char, 5, std_allocator_noprop>& buffer) {
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
const char* original_data_ptr = &buffer[0];
basic_memory_buffer<char, 5, std_allocator_noprop> buffer2(
std::move(buffer));
const char* new_data_ptr = &buffer2[0];
EXPECT_NE(new_data_ptr, original_data_ptr);
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
// Allocators should NOT be transferred; they remain distinct instances.
// The original buffer's allocator pointer should still be valid (not
// nullptr).
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
};
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
buffer.append(string_view("test", 4));
check_move_buffer("test", buffer);
buffer.push_back('a');
check_move_buffer("testa", buffer);
}
TEST(memory_buffer_test, move_ctor_dynamic_buffer_non_propagating) {
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
const char test[] = "test";
buffer.append(test, test + 4);
const char* inline_buffer_ptr = &buffer[0];
buffer.push_back('a');
EXPECT_NE(buffer.data(), inline_buffer_ptr);
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer2;
buffer2 = std::move(buffer);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testa");
EXPECT_GT(buffer2.capacity(), 4u);
EXPECT_NE(buffer2.data(), inline_buffer_ptr);
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
}
void check_move_assign_buffer(const char* str, void check_move_assign_buffer(const char* str,
basic_memory_buffer<char, 5>& buffer) { basic_memory_buffer<char, 5>& buffer) {
basic_memory_buffer<char, 5> buffer2; basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer); buffer2 = std::move(buffer);
// Move shouldn't destroy the inline content of the first buffer. // Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(str, std::string(&buffer[0], buffer.size())); EXPECT_EQ(std::string(&buffer[0], buffer.size()), str);
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size())); EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
EXPECT_EQ(5u, buffer2.capacity()); EXPECT_EQ(buffer2.capacity(), 5u);
} }
TEST(memory_buffer_test, move_assignment) { TEST(memory_buffer_test, move_assignment) {
@ -371,8 +425,8 @@ TEST(memory_buffer_test, move_assignment) {
basic_memory_buffer<char, 5> buffer2; basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer); buffer2 = std::move(buffer);
// Move should rip the guts of the first buffer. // Move should rip the guts of the first buffer.
EXPECT_EQ(inline_buffer_ptr, &buffer[0]); EXPECT_EQ(buffer.data(), inline_buffer_ptr);
EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size())); EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testab");
EXPECT_GT(buffer2.capacity(), 5u); EXPECT_GT(buffer2.capacity(), 5u);
} }
@ -471,6 +525,18 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
EXPECT_THROW(buffer.resize(161), std::exception); EXPECT_THROW(buffer.resize(161), std::exception);
} }
TEST(memory_buffer_test, back_insert_iterator) {
fmt::memory_buffer buf;
using iterator = decltype(std::back_inserter(buf));
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<iterator>::value);
}
TEST(format_test, digits2_alignment) {
auto p =
fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0));
EXPECT_EQ(p % 2, 0);
}
TEST(format_test, exception_from_lib) { TEST(format_test, exception_from_lib) {
EXPECT_THROW_MSG(fmt::report_error("test"), format_error, "test"); EXPECT_THROW_MSG(fmt::report_error("test"), format_error, "test");
} }
@ -536,6 +602,10 @@ TEST(format_test, arg_errors) {
format_error, "argument not found"); format_error, "argument not found");
} }
TEST(format_test, display_width_precision) {
EXPECT_EQ(fmt::format("{:.5}", "🐱🐱🐱"), "🐱🐱");
}
template <int N> struct test_format { template <int N> struct test_format {
template <typename... T> template <typename... T>
static auto format(fmt::string_view fmt, const T&... args) -> std::string { static auto format(fmt::string_view fmt, const T&... args) -> std::string {
@ -566,6 +636,9 @@ TEST(format_test, named_arg) {
EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'), EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
fmt::arg("A_", "A"), fmt::arg("_1", 1))); fmt::arg("A_", "A"), fmt::arg("_1", 1)));
EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42"); EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42");
EXPECT_EQ(fmt::format("{value:{width}}", fmt::arg("value", -42),
fmt::arg("width", 4)),
" -42");
EXPECT_EQ("st", EXPECT_EQ("st",
fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2))); fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2"); EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");
@ -583,6 +656,9 @@ TEST(format_test, named_arg) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{a} {}"), fmt::arg("a", 2), 42), EXPECT_THROW_MSG((void)fmt::format(runtime("{a} {}"), fmt::arg("a", 2), 42),
format_error, format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(
(void)fmt::format("{a}", fmt::arg("a", 1), fmt::arg("a", 10)),
format_error, "duplicate named arg");
} }
TEST(format_test, auto_arg_index) { TEST(format_test, auto_arg_index) {
@ -794,7 +870,7 @@ TEST(format_test, hash_flag) {
EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50"); EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50");
EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0."); EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0.");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error,
"missing '}' in format string"); "invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
@ -815,7 +891,7 @@ TEST(format_test, zero_flag) {
EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042"); EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042");
EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042"); EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error,
"missing '}' in format string"); "invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error,
@ -855,6 +931,7 @@ TEST(format_test, width) {
" 0xcafe"); " 0xcafe");
EXPECT_EQ(fmt::format("{:11}", 'x'), "x "); EXPECT_EQ(fmt::format("{:11}", 'x'), "x ");
EXPECT_EQ(fmt::format("{:12}", "str"), "str "); EXPECT_EQ(fmt::format("{:12}", "str"), "str ");
EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "*🤡**");
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**"); EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**"); EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42."); EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
@ -862,6 +939,35 @@ TEST(format_test, width) {
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0"); EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
} }
TEST(format_test, debug_presentation) {
EXPECT_EQ(fmt::format("{:?}", ""), R"("")");
EXPECT_EQ(fmt::format("{:*<5.0?}", "\n"), R"(*****)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "\n"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "\n"), R"("\***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "\n"), R"("\n**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", "\n"), R"("\n"*)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "Σ"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "Σ"), R"("Σ***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "Σ"), R"("Σ"**)");
EXPECT_EQ(fmt::format("{:*<5.1?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.3?}", ""), R"("**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", ""), R"(""*)");
EXPECT_EQ(fmt::format("{:*<8?}", "туда"), R"("туда"**)");
EXPECT_EQ(fmt::format("{:*>8?}", "сюда"), R"(**"сюда")");
EXPECT_EQ(fmt::format("{:*^8?}", "中心"), R"(*""*)");
EXPECT_EQ(fmt::format("{:*^14?}", "A\t👈🤯ы猫"), R"(*"A\t👈🤯ы"*)");
}
auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES
? "width/precision is out of range"
: "width/precision is not integer";
TEST(format_test, runtime_width) { TEST(format_test, runtime_width) {
auto int_maxer = std::to_string(INT_MAX + 1u); auto int_maxer = std::to_string(INT_MAX + 1u);
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0),
@ -884,23 +990,23 @@ TEST(format_test, runtime_width) {
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error,
"negative width"); "width/precision is out of range");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)),
format_error, "number is too big"); format_error, bad_dynamic_spec_msg);
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error,
"negative width"); bad_dynamic_spec_msg);
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX; long value = INT_MAX;
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)),
format_error, "number is too big"); format_error, bad_dynamic_spec_msg);
} }
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)),
format_error, "number is too big"); format_error, bad_dynamic_spec_msg);
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error,
"width is not integer"); "width/precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
"width is not integer"); "width/precision is not integer");
EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42"); EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42");
EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), " 42"); EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), " 42");
@ -917,6 +1023,10 @@ TEST(format_test, runtime_width) {
EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42"); EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42");
} }
TEST(format_test, exponent_range) {
for (int e = -1074; e <= 1023; ++e) (void)fmt::format("{}", std::ldexp(1, e));
}
TEST(format_test, precision) { TEST(format_test, precision) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:.%u", UINT_MAX); safe_sprintf(format_str, "{0:.%u", UINT_MAX);
@ -939,7 +1049,7 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error,
"invalid precision"); "invalid precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error,
"invalid precision"); "invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
"invalid format specifier"); "invalid format specifier");
@ -1044,7 +1154,8 @@ TEST(format_test, precision) {
EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123."); EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123.");
EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23"); EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23");
EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001"); EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400"); EXPECT_EQ(fmt::format("{}", 123456789.0f), "1.2345679e+08");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1.0196664e+09");
EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01"); EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34"); EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
@ -1054,21 +1165,45 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)), (void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
format_error, "invalid format specifier"); format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304), (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.f}"), 42.0), format_error,
"invalid format string");
EXPECT_EQ(fmt::format("{0:.2}", "str"), "st"); EXPECT_EQ(fmt::format("{0:.2}", "str"), "st");
EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык"); EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык");
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456"); EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
} }
TEST(xchar_test, utf8_precision) { TEST(format_test, large_precision) {
// Iterator used to abort the actual output.
struct throwing_iterator {
auto operator=(char) -> throwing_iterator& {
throw std::runtime_error("aborted");
return *this;
}
auto operator*() -> throwing_iterator& { return *this; }
auto operator++() -> throwing_iterator& { return *this; }
auto operator++(int) -> throwing_iterator { return *this; }
};
auto it = throwing_iterator();
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}}"), 1.0,
fmt::detail::max_value<int>()),
std::runtime_error, "aborted");
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}e}"), 1.0,
fmt::detail::max_value<int>() - 1),
std::runtime_error, "aborted");
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
}
TEST(format_test, utf8_precision) {
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
EXPECT_EQ(fmt::detail::compute_width(result), 4);
EXPECT_EQ(result, "caf\u00e9"); EXPECT_EQ(result, "caf\u00e9");
} }
@ -1103,23 +1238,23 @@ TEST(format_test, runtime_precision) {
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1),
format_error, "negative precision"); format_error, "width/precision is out of range");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)),
format_error, "number is too big"); format_error, bad_dynamic_spec_msg);
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l),
format_error, "negative precision"); format_error, bad_dynamic_spec_msg);
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX; long value = INT_MAX;
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)),
format_error, "number is too big"); format_error, bad_dynamic_spec_msg);
} }
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)),
format_error, "number is too big"); format_error, bad_dynamic_spec_msg);
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'),
format_error, "precision is not integer"); format_error, "width/precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0),
format_error, "precision is not integer"); format_error, "width/precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
"invalid format specifier"); "invalid format specifier");
@ -1650,6 +1785,20 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
EXPECT_EQ("'foo'", EXPECT_EQ("'foo'",
fmt::format("{}", explicitly_convertible_to_std_string_view())); fmt::format("{}", explicitly_convertible_to_std_string_view()));
} }
struct convertible_to_std_string_view {
operator std::string_view() const noexcept { return "Hi there"; }
};
FMT_BEGIN_NAMESPACE
template <>
class formatter<convertible_to_std_string_view>
: public formatter<std::string_view> {};
FMT_END_NAMESPACE
TEST(format_test, format_implicitly_convertible_and_inherits_string_view) {
static_assert(fmt::is_formattable<convertible_to_std_string_view>{}, "");
EXPECT_EQ("Hi there", fmt::format("{}", convertible_to_std_string_view{}));
}
#endif #endif
class Answer {}; class Answer {};
@ -1708,19 +1857,15 @@ TEST(format_test, format_examples) {
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
EXPECT_EQ("The answer is 42.", to_string(out)); EXPECT_EQ("The answer is 42.", to_string(out));
const char* filename = "nonexistent"; EXPECT_THROW(
FILE* ftest = safe_fopen(filename, "r");
if (ftest) fclose(ftest);
int error_code = errno;
EXPECT_TRUE(ftest == nullptr);
EXPECT_SYSTEM_ERROR(
{ {
FILE* f = safe_fopen(filename, "r"); const char* filename = "madeup";
if (!f) FILE* file = fopen(filename, "r");
throw fmt::system_error(errno, "Cannot open file '{}'", filename); if (!file)
fclose(f); throw fmt::system_error(errno, "cannot open file '{}'", filename);
fclose(file);
}, },
error_code, "Cannot open file 'nonexistent'"); std::system_error);
EXPECT_EQ("First, thou shalt count to three", EXPECT_EQ("First, thou shalt count to three",
fmt::format("First, thou shalt count to {0}", "three")); fmt::format("First, thou shalt count to {0}", "three"));
@ -1780,33 +1925,6 @@ TEST(format_test, big_print) {
EXPECT_WRITE(stdout, big_print(), std::string(count, 'x')); EXPECT_WRITE(stdout, big_print(), std::string(count, 'x'));
} }
// Windows CRT implements _IOLBF incorrectly (full buffering).
#if FMT_USE_FCNTL && !defined(_WIN32)
TEST(format_test, line_buffering) {
auto pipe = fmt::pipe();
int write_fd = pipe.write_end.descriptor();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
write_end.print("42\n");
close(write_fd);
try {
write_end.close();
} catch (const std::system_error&) {
}
auto read_end = pipe.read_end.fdopen("r");
std::thread reader([&]() {
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
(void)result;
EXPECT_EQ(n, 42);
});
reader.join();
}
#endif
struct deadlockable { struct deadlockable {
int value = 0; int value = 0;
mutable std::mutex mutex; mutable std::mutex mutex;
@ -1918,11 +2036,6 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g')); 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
} }
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
static constexpr const char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) { TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo"); EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
@ -1937,19 +2050,12 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2"); EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
#endif #endif
(void)static_with_null; static constexpr char format_str[3] = {'{', '}', '\0'};
(void)static_no_null; (void)format_str;
#ifndef _MSC_VER #ifndef _MSC_VER
EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
#endif #endif
(void)with_null;
(void)no_null;
#if FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
#endif
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
#endif #endif
@ -1965,7 +2071,6 @@ TEST(format_test, custom_format_compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42"); EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42");
} }
#if FMT_USE_USER_DEFINED_LITERALS
TEST(format_test, named_arg_udl) { TEST(format_test, named_arg_udl) {
using namespace fmt::literals; using namespace fmt::literals;
auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra", auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra",
@ -1977,13 +2082,12 @@ TEST(format_test, named_arg_udl) {
EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42"); EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42");
} }
#endif // FMT_USE_USER_DEFINED_LITERALS
TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); } TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); }
TEST(format_test, formatter_not_specialized) { TEST(format_test, formatter_not_specialized) {
static_assert(!fmt::has_formatter<fmt::formatter<test_enum>, static_assert(!fmt::is_formattable<fmt::formatter<test_enum>,
fmt::format_context>::value, fmt::format_context>::value,
""); "");
} }
@ -1992,6 +2096,8 @@ enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
auto format_as(big_enum e) -> unsigned long long { return e; } auto format_as(big_enum e) -> unsigned long long { return e; }
TEST(format_test, strong_enum) { TEST(format_test, strong_enum) {
auto arg = fmt::basic_format_arg<fmt::context>(big_enum_value);
EXPECT_EQ(arg.type(), fmt::detail::type::ulong_long_type);
EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000"); EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000");
} }
#endif #endif
@ -2041,6 +2147,17 @@ TEST(format_test, output_iterators) {
std::stringstream s; std::stringstream s;
fmt::format_to(std::ostream_iterator<char>(s), "{}", 42); fmt::format_to(std::ostream_iterator<char>(s), "{}", 42);
EXPECT_EQ("42", s.str()); EXPECT_EQ("42", s.str());
std::stringstream s2;
fmt::format_to(std::ostreambuf_iterator<char>(s2), "{}.{:06d}", 42, 43);
EXPECT_EQ("42.000043", s2.str());
}
TEST(format_test, fill_via_appender) {
fmt::memory_buffer buf;
auto it = fmt::appender(buf);
std::fill_n(it, 3, '~');
EXPECT_EQ(fmt::to_string(buf), "~~~");
} }
TEST(format_test, formatted_size) { TEST(format_test, formatted_size) {
@ -2156,7 +2273,7 @@ TEST(format_test, vformat_to) {
fmt::vformat_to(std::back_inserter(s), "{}", args); fmt::vformat_to(std::back_inserter(s), "{}", args);
EXPECT_EQ(s, "42"); EXPECT_EQ(s, "42");
s.clear(); s.clear();
fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args); fmt::vformat_to(std::back_inserter(s), "{}", args);
EXPECT_EQ(s, "42"); EXPECT_EQ(s, "42");
} }
@ -2365,6 +2482,7 @@ namespace adl_test {
template <typename... T> void make_format_args(const T&...) = delete; template <typename... T> void make_format_args(const T&...) = delete;
struct string : std::string {}; struct string : std::string {};
auto format_as(const string& s) -> std::string { return s; }
} // namespace adl_test } // namespace adl_test
// Test that formatting functions compile when make_format_args is found by ADL. // Test that formatting functions compile when make_format_args is found by ADL.
@ -2420,3 +2538,130 @@ TEST(format_test, formatter_overrides_implicit_conversion) {
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "x"); EXPECT_EQ(fmt::format("{}", convertible_to_int()), "x");
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "y"); EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "y");
} }
struct ustring {
using value_type = unsigned;
auto find_first_of(value_type, size_t) const -> size_t;
auto data() const -> const char*;
auto size() const -> size_t;
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<ustring> : formatter<std::string> {
auto format(const ustring&, format_context& ctx) const
-> decltype(ctx.out()) {
return formatter<std::string>::format("ustring", ctx);
}
};
FMT_END_NAMESPACE
TEST(format_test, ustring) {
EXPECT_EQ(fmt::format("{}", ustring()), "ustring");
}
TEST(format_test, writer) {
auto write_to_stdout = []() {
auto w = fmt::writer(stdout);
w.print("{}", 42);
};
EXPECT_WRITE(stdout, write_to_stdout(), "42");
#if FMT_USE_FCNTL
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
fmt::writer(write_end.get()).print("42");
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
(void)result;
EXPECT_EQ(n, 42);
#endif
auto s = fmt::string_buffer();
fmt::writer(s).print("foo");
EXPECT_EQ(s.str(), "foo");
}
#if FMT_USE_FCNTL && !defined(_WIN32)
TEST(format_test, invalid_glibc_buffer) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
auto file = write_end.get();
// This results in _IO_write_ptr < _IO_write_end.
fprintf(file, "111\n");
setvbuf(file, nullptr, _IOLBF, 0);
fmt::print(file, "------\n");
}
#endif // FMT_USE_FCNTL
#if FMT_USE_BITINT
FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension")
TEST(format_test, bitint) {
using fmt::detail::bitint;
using fmt::detail::ubitint;
EXPECT_EQ(fmt::format("{}", ubitint<3>(7)), "7");
EXPECT_EQ(fmt::format("{}", bitint<7>()), "0");
EXPECT_EQ(fmt::format("{}", ubitint<15>(31000)), "31000");
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MIN)), "-32768");
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MAX)), "32767");
EXPECT_EQ(fmt::format("{}", ubitint<32>(4294967295)), "4294967295");
EXPECT_EQ(fmt::format("{}", ubitint<47>(140737488355327ULL)),
"140737488355327");
EXPECT_EQ(fmt::format("{}", bitint<47>(-40737488355327LL)),
"-40737488355327");
// Check lvalues and const
auto a = bitint<8>(0);
auto b = ubitint<32>(4294967295);
const auto c = bitint<7>(0);
const auto d = ubitint<32>(4294967295);
EXPECT_EQ(fmt::format("{}", a), "0");
EXPECT_EQ(fmt::format("{}", b), "4294967295");
EXPECT_EQ(fmt::format("{}", c), "0");
EXPECT_EQ(fmt::format("{}", d), "4294967295");
static_assert(fmt::is_formattable<bitint<64>, char>{}, "");
static_assert(fmt::is_formattable<ubitint<64>, char>{}, "");
}
#endif
#ifdef __cpp_lib_byte
TEST(base_test, format_byte) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
EXPECT_EQ(s, "42");
}
#endif
// Only defined after the test case.
struct incomplete_type;
extern const incomplete_type& external_instance;
FMT_BEGIN_NAMESPACE
template <> struct formatter<incomplete_type> : formatter<int> {
auto format(const incomplete_type& x, context& ctx) const -> appender;
};
FMT_END_NAMESPACE
TEST(incomplete_type_test, format) {
EXPECT_EQ(fmt::format("{}", external_instance), "42");
}
struct incomplete_type {};
const incomplete_type& external_instance = {};
auto fmt::formatter<incomplete_type>::format(const incomplete_type&,
fmt::context& ctx) const
-> fmt::appender {
return formatter<int>::format(42, ctx);
}

View File

@ -12,10 +12,10 @@ void invoke_inner(fmt::string_view format_str, Rep rep) {
auto value = std::chrono::duration<Rep, Period>(rep); auto value = std::chrono::duration<Rep, Period>(rep);
try { try {
#if FMT_FUZZ_FORMAT_TO_STRING #if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, value); std::string message = fmt::format(fmt::runtime(format_str), value);
#else #else
auto buf = fmt::memory_buffer(); auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), format_str, value); fmt::format_to(std::back_inserter(buf), fmt::runtime(format_str), value);
#endif #endif
} catch (std::exception&) { } catch (std::exception&) {
} }

View File

@ -22,7 +22,7 @@
#define FMT_FUZZ_SEPARATE_ALLOCATION 1 #define FMT_FUZZ_SEPARATE_ALLOCATION 1
// The size of the largest possible type in use. // The size of the largest possible type in use.
// To let the the fuzzer mutation be efficient at cross pollinating between // To let the fuzzer mutation be efficient at cross pollinating between
// different types, use a fixed size format. The same bit pattern, interpreted // different types, use a fixed size format. The same bit pattern, interpreted
// as another type, is likely interesting. // as another type, is likely interesting.
constexpr auto fixed_size = 16; constexpr auto fixed_size = 16;

View File

@ -15,6 +15,10 @@
#include "fmt/os.h" #include "fmt/os.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
#ifdef _MSC_VER
# include <crtdbg.h>
#endif
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \

View File

@ -13360,6 +13360,7 @@ bool UnorderedElementsAreMatcherImplBase::FindPairing(
#include <memory> #include <memory>
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
@ -13983,46 +13984,50 @@ MockObjectRegistry g_mock_object_registry;
// Maps a mock object to the reaction Google Mock should have when an // Maps a mock object to the reaction Google Mock should have when an
// uninteresting method is called. Protected by g_gmock_mutex. // uninteresting method is called. Protected by g_gmock_mutex.
std::map<const void*, internal::CallReaction> g_uninteresting_call_reaction; std::unordered_map<uintptr_t, internal::CallReaction>&
UninterestingCallReactionMap() {
static auto* map = new std::unordered_map<uintptr_t, internal::CallReaction>;
return *map;
}
// Sets the reaction Google Mock should have when an uninteresting // Sets the reaction Google Mock should have when an uninteresting
// method of the given mock object is called. // method of the given mock object is called.
void SetReactionOnUninterestingCalls(const void* mock_obj, void SetReactionOnUninterestingCalls(uintptr_t mock_obj,
internal::CallReaction reaction) internal::CallReaction reaction)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
internal::MutexLock l(&internal::g_gmock_mutex); internal::MutexLock l(&internal::g_gmock_mutex);
g_uninteresting_call_reaction[mock_obj] = reaction; UninterestingCallReactionMap()[mock_obj] = reaction;
} }
} // namespace } // namespace
// Tells Google Mock to allow uninteresting calls on the given mock // Tells Google Mock to allow uninteresting calls on the given mock
// object. // object.
void Mock::AllowUninterestingCalls(const void* mock_obj) void Mock::AllowUninterestingCalls(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
SetReactionOnUninterestingCalls(mock_obj, internal::kAllow); SetReactionOnUninterestingCalls(mock_obj, internal::kAllow);
} }
// Tells Google Mock to warn the user about uninteresting calls on the // Tells Google Mock to warn the user about uninteresting calls on the
// given mock object. // given mock object.
void Mock::WarnUninterestingCalls(const void* mock_obj) void Mock::WarnUninterestingCalls(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
SetReactionOnUninterestingCalls(mock_obj, internal::kWarn); SetReactionOnUninterestingCalls(mock_obj, internal::kWarn);
} }
// Tells Google Mock to fail uninteresting calls on the given mock // Tells Google Mock to fail uninteresting calls on the given mock
// object. // object.
void Mock::FailUninterestingCalls(const void* mock_obj) void Mock::FailUninterestingCalls(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
SetReactionOnUninterestingCalls(mock_obj, internal::kFail); SetReactionOnUninterestingCalls(mock_obj, internal::kFail);
} }
// Tells Google Mock the given mock object is being destroyed and its // Tells Google Mock the given mock object is being destroyed and its
// entry in the call-reaction table should be removed. // entry in the call-reaction table should be removed.
void Mock::UnregisterCallReaction(const void* mock_obj) void Mock::UnregisterCallReaction(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
internal::MutexLock l(&internal::g_gmock_mutex); internal::MutexLock l(&internal::g_gmock_mutex);
g_uninteresting_call_reaction.erase(mock_obj); UninterestingCallReactionMap().erase(static_cast<uintptr_t>(mock_obj));
} }
// Returns the reaction Google Mock will have on uninteresting calls // Returns the reaction Google Mock will have on uninteresting calls
@ -14031,9 +14036,12 @@ internal::CallReaction Mock::GetReactionOnUninterestingCalls(
const void* mock_obj) const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) { GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
internal::MutexLock l(&internal::g_gmock_mutex); internal::MutexLock l(&internal::g_gmock_mutex);
return (g_uninteresting_call_reaction.count(mock_obj) == 0) ? return (UninterestingCallReactionMap().count(
internal::intToCallReaction(GMOCK_FLAG(default_mock_behavior)) : reinterpret_cast<uintptr_t>(mock_obj)) == 0)
g_uninteresting_call_reaction[mock_obj]; ? internal::intToCallReaction(
GMOCK_FLAG(default_mock_behavior))
: UninterestingCallReactionMap()[reinterpret_cast<uintptr_t>(
mock_obj)];
} }
// Tells Google Mock to ignore mock_obj when checking for leaked mock // Tells Google Mock to ignore mock_obj when checking for leaked mock

View File

@ -2860,6 +2860,7 @@ GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ #ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_ #define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
#include <cstdint>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
@ -8646,22 +8647,22 @@ class GTEST_API_ Mock {
// Tells Google Mock to allow uninteresting calls on the given mock // Tells Google Mock to allow uninteresting calls on the given mock
// object. // object.
static void AllowUninterestingCalls(const void* mock_obj) static void AllowUninterestingCalls(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Tells Google Mock to warn the user about uninteresting calls on // Tells Google Mock to warn the user about uninteresting calls on
// the given mock object. // the given mock object.
static void WarnUninterestingCalls(const void* mock_obj) static void WarnUninterestingCalls(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Tells Google Mock to fail uninteresting calls on the given mock // Tells Google Mock to fail uninteresting calls on the given mock
// object. // object.
static void FailUninterestingCalls(const void* mock_obj) static void FailUninterestingCalls(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Tells Google Mock the given mock object is being destroyed and // Tells Google Mock the given mock object is being destroyed and
// its entry in the call-reaction table should be removed. // its entry in the call-reaction table should be removed.
static void UnregisterCallReaction(const void* mock_obj) static void UnregisterCallReaction(uintptr_t mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex); GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Returns the reaction Google Mock will have on uninteresting calls // Returns the reaction Google Mock will have on uninteresting calls
@ -11417,6 +11418,7 @@ MATCHER(IsFalse, negation ? "is true" : "is false") {
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_ #ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_ #define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
#include <cstdint>
#include <type_traits> #include <type_traits>
@ -11461,25 +11463,37 @@ constexpr bool HasStrictnessModifier() {
template <typename Base> template <typename Base>
class NiceMockImpl { class NiceMockImpl {
public: public:
NiceMockImpl() { ::testing::Mock::AllowUninterestingCalls(this); } NiceMockImpl() {
::testing::Mock::AllowUninterestingCalls(reinterpret_cast<uintptr_t>(this));
}
~NiceMockImpl() { ::testing::Mock::UnregisterCallReaction(this); } ~NiceMockImpl() {
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
}
}; };
template <typename Base> template <typename Base>
class NaggyMockImpl { class NaggyMockImpl {
public: public:
NaggyMockImpl() { ::testing::Mock::WarnUninterestingCalls(this); } NaggyMockImpl() {
::testing::Mock::WarnUninterestingCalls(reinterpret_cast<uintptr_t>(this));
}
~NaggyMockImpl() { ::testing::Mock::UnregisterCallReaction(this); } ~NaggyMockImpl() {
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
}
}; };
template <typename Base> template <typename Base>
class StrictMockImpl { class StrictMockImpl {
public: public:
StrictMockImpl() { ::testing::Mock::FailUninterestingCalls(this); } StrictMockImpl() {
::testing::Mock::FailUninterestingCalls(reinterpret_cast<uintptr_t>(this));
}
~StrictMockImpl() { ::testing::Mock::UnregisterCallReaction(this); } ~StrictMockImpl() {
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
}
}; };
} // namespace internal } // namespace internal

View File

@ -6390,17 +6390,18 @@ class MatcherBase : private MatcherDescriberInterface {
} }
protected: protected:
MatcherBase() : vtable_(nullptr) {} MatcherBase() : vtable_(nullptr), buffer_() {}
// Constructs a matcher from its implementation. // Constructs a matcher from its implementation.
template <typename U> template <typename U>
explicit MatcherBase(const MatcherInterface<U>* impl) { explicit MatcherBase(const MatcherInterface<U>* impl)
: vtable_(nullptr), buffer_() {
Init(impl); Init(impl);
} }
template <typename M, typename = typename std::remove_reference< template <typename M, typename = typename std::remove_reference<
M>::type::is_gtest_matcher> M>::type::is_gtest_matcher>
MatcherBase(M&& m) { // NOLINT MatcherBase(M&& m) : vtable_(nullptr), buffer_() { // NOLINT
Init(std::forward<M>(m)); Init(std::forward<M>(m));
} }

View File

@ -37,7 +37,7 @@ template <typename T> class mock_allocator {
MOCK_METHOD(void, deallocate, (T*, size_t)); MOCK_METHOD(void, deallocate, (T*, size_t));
}; };
template <typename Allocator> class allocator_ref { template <typename Allocator, bool PropagateOnMove = true> class allocator_ref {
private: private:
Allocator* alloc_; Allocator* alloc_;
@ -48,6 +48,8 @@ template <typename Allocator> class allocator_ref {
public: public:
using value_type = typename Allocator::value_type; using value_type = typename Allocator::value_type;
using propagate_on_container_move_assignment =
fmt::bool_constant<PropagateOnMove>;
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {} explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
@ -66,12 +68,21 @@ template <typename Allocator> class allocator_ref {
} }
public: public:
Allocator* get() const { return alloc_; } auto get() const -> Allocator* { return alloc_; }
value_type* allocate(size_t n) { auto allocate(size_t n) -> value_type* {
return std::allocator_traits<Allocator>::allocate(*alloc_, n); return std::allocator_traits<Allocator>::allocate(*alloc_, n);
} }
void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); } void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
friend auto operator==(allocator_ref a, allocator_ref b) noexcept -> bool {
if (a.alloc_ == b.alloc_) return true;
return a.alloc_ && b.alloc_ && *a.alloc_ == *b.alloc_;
}
friend auto operator!=(allocator_ref a, allocator_ref b) noexcept -> bool {
return !(a == b);
}
}; };
#endif // FMT_MOCK_ALLOCATOR_H_ #endif // FMT_MOCK_ALLOCATOR_H_

View File

@ -0,0 +1,25 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "gtest/gtest.h"
#if !defined(__GNUC__) || __GNUC__ >= 5
# define FMT_BUILTIN_TYPES 0
# include "fmt/format.h"
TEST(no_builtin_types_test, format) {
EXPECT_EQ(fmt::format("{}", 42), "42");
EXPECT_EQ(fmt::format("{}", 42L), "42");
}
TEST(no_builtin_types_test, double_is_custom_type) {
double d = 42;
auto args = fmt::make_format_args(d);
EXPECT_EQ(fmt::format_args(args).get(0).type(),
fmt::detail::type::custom_type);
}
#endif

View File

@ -10,6 +10,7 @@
#include <cstdlib> // std::exit #include <cstdlib> // std::exit
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <thread>
#include "gtest-extra.h" #include "gtest-extra.h"
#include "util.h" #include "util.h"
@ -18,10 +19,21 @@ using fmt::buffered_file;
using testing::HasSubstr; using testing::HasSubstr;
using wstring_view = fmt::basic_string_view<wchar_t>; using wstring_view = fmt::basic_string_view<wchar_t>;
static std::string uniq_file_name(unsigned line_number) { static auto uniq_file_name(unsigned line_number) -> std::string {
return "test-file" + std::to_string(line_number); return "test-file" + std::to_string(line_number);
} }
auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
@ -236,8 +248,7 @@ TEST(buffered_file_test, descriptor) {
} }
TEST(ostream_test, move) { TEST(ostream_test, move) {
auto test_file = uniq_file_name(__LINE__); fmt::ostream out = fmt::output_file(uniq_file_name(__LINE__));
fmt::ostream out = fmt::output_file(test_file);
fmt::ostream moved(std::move(out)); fmt::ostream moved(std::move(out));
moved.print("hello"); moved.print("hello");
} }
@ -429,8 +440,7 @@ TEST(file_test, read) {
} }
TEST(file_test, read_error) { TEST(file_test, read_error) {
auto test_file = uniq_file_name(__LINE__); file f(uniq_file_name(__LINE__), file::WRONLY | file::CREATE);
file f(test_file, file::WRONLY | file::CREATE);
char buf; char buf;
// We intentionally read from a file opened in the write-only mode to // We intentionally read from a file opened in the write-only mode to
// cause error. // cause error.
@ -439,15 +449,13 @@ TEST(file_test, read_error) {
TEST(file_test, write) { TEST(file_test, write) {
auto pipe = fmt::pipe(); auto pipe = fmt::pipe();
auto test_file = uniq_file_name(__LINE__); write(pipe.write_end, "test");
write(pipe.write_end, test_file);
pipe.write_end.close(); pipe.write_end.close();
EXPECT_READ(pipe.read_end, test_file); EXPECT_READ(pipe.read_end, "test");
} }
TEST(file_test, write_error) { TEST(file_test, write_error) {
auto test_file = uniq_file_name(__LINE__); file f(uniq_file_name(__LINE__), file::RDONLY | file::CREATE);
file f(test_file, file::RDONLY | file::CREATE);
// We intentionally write to a file opened in the read-only mode to // We intentionally write to a file opened in the read-only mode to
// cause error. // cause error.
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file"); EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
@ -513,4 +521,79 @@ TEST(file_test, fdopen) {
int read_fd = pipe.read_end.descriptor(); int read_fd = pipe.read_end.descriptor();
EXPECT_EQ(read_fd, FMT_POSIX(fileno(pipe.read_end.fdopen("r").get()))); EXPECT_EQ(read_fd, FMT_POSIX(fileno(pipe.read_end.fdopen("r").get())));
} }
// Windows CRT implements _IOLBF incorrectly (full buffering).
# ifndef _WIN32
TEST(file_test, line_buffering) {
auto pipe = fmt::pipe();
int write_fd = pipe.write_end.descriptor();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
write_end.print("42\n");
close(write_fd);
try {
write_end.close();
} catch (const std::system_error&) {
}
auto read_end = pipe.read_end.fdopen("r");
std::thread reader([&]() {
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
(void)result;
EXPECT_EQ(n, 42);
});
reader.join();
}
# endif // _WIN32
TEST(file_test, buffer_boundary) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
for (int i = 3; i < 4094; i++)
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
write_end.print("{} {}", 1234, 567);
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
char buf[4091] = {};
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
EXPECT_EQ(n, sizeof(buf));
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
}
TEST(file_test, io_putting) {
auto pipe = fmt::pipe();
auto read_end = pipe.read_end.fdopen("r");
auto write_end = pipe.write_end.fdopen("w");
size_t read_size = 0;
auto reader = std::thread([&]() {
size_t n = 0;
do {
char buf[4096] = {};
n = fread(buf, 1, sizeof(buf), read_end.get());
read_size += n;
} while (n != 0);
});
// This initialize buffers but doesn't set _IO_CURRENTLY_PUTTING.
fseek(write_end.get(), 0, SEEK_SET);
size_t write_size = 0;
for (int i = 0; i <= 20000; ++i) {
auto s = fmt::format("{}\n", i);
fmt::print(write_end.get(), "{}", s);
write_size += s.size();
}
write_end.close();
reader.join();
EXPECT_EQ(read_size, write_size);
}
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL

View File

@ -265,7 +265,7 @@ template <> struct formatter<abstract> : ostream_formatter {};
} // namespace fmt } // namespace fmt
void format_abstract_compiles(const abstract& a) { void format_abstract_compiles(const abstract& a) {
fmt::format(FMT_COMPILE("{}"), a); (void)fmt::format(FMT_COMPILE("{}"), a);
} }
TEST(ostream_test, is_formattable) { TEST(ostream_test, is_formattable) {

25
test/perf-sanity.cc Normal file
View File

@ -0,0 +1,25 @@
// A quick and dirty performance test.
// For actual benchmarks see https://github.com/fmtlib/format-benchmark.
#include <atomic>
#include <chrono>
#include <iterator>
#include "fmt/format.h"
int main() {
const int n = 10000000;
auto start = std::chrono::steady_clock::now();
for (int iteration = 0; iteration < n; ++iteration) {
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf),
"Hello, {}. The answer is {} and {}.", 1, 2345, 6789);
}
std::atomic_signal_fence(std::memory_order_acq_rel); // Clobber memory.
auto end = std::chrono::steady_clock::now();
// Print time in milliseconds.
std::chrono::duration<double> duration = end - start;
fmt::print("{:.1f}\n", duration.count() * 1000);
}

View File

@ -6,16 +6,12 @@
// For the license information refer to format.h. // For the license information refer to format.h.
#include "fmt/printf.h" #include "fmt/printf.h"
// include <format> if possible for https://github.com/fmtlib/fmt/pull/4042
#if FMT_HAS_INCLUDE(<format>) && FMT_CPLUSPLUS > 201703L
# include <format>
#endif
#include <cctype> #include <cctype>
#include <climits> #include <climits>
#include <cstring> #include <cstring>
#include "fmt/xchar.h" #include "fmt/xchar.h" // DEPRECATED!
#include "gtest-extra.h" #include "gtest-extra.h"
#include "util.h" #include "util.h"
@ -26,27 +22,21 @@ using fmt::detail::max_value;
const unsigned big_num = INT_MAX + 1u; const unsigned big_num = INT_MAX + 1u;
// Makes format string argument positional. // Makes format string argument positional.
static std::string make_positional(fmt::string_view format) { static auto make_positional(fmt::string_view format) -> std::string {
std::string s(format.data(), format.size()); std::string s(format.data(), format.size());
s.replace(s.find('%'), 1, "%1$"); s.replace(s.find('%'), 1, "%1$");
return s; return s;
} }
static std::wstring make_positional(fmt::basic_string_view<wchar_t> format) {
std::wstring s(format.data(), format.size());
s.replace(s.find(L'%'), 1, L"%1$");
return s;
}
// A wrapper around fmt::sprintf to workaround bogus warnings about invalid // A wrapper around fmt::sprintf to workaround bogus warnings about invalid
// format strings in MSVC. // format strings in MSVC.
template <typename... Args> template <typename... Args>
std::string test_sprintf(fmt::string_view format, const Args&... args) { auto test_sprintf(fmt::string_view format, const Args&... args) -> std::string {
return fmt::sprintf(format, args...); return fmt::sprintf(format, args...);
} }
template <typename... Args> template <typename... Args>
std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format, auto test_sprintf(fmt::basic_string_view<wchar_t> format, const Args&... args)
const Args&... args) { -> std::wstring {
return fmt::sprintf(format, args...); return fmt::sprintf(format, args...);
} }
@ -55,10 +45,7 @@ std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
<< "format: " << format; \ << "format: " << format; \
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg)) EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
TEST(printf_test, no_args) { TEST(printf_test, no_args) { EXPECT_EQ("test", test_sprintf("test")); }
EXPECT_EQ("test", test_sprintf("test"));
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
}
TEST(printf_test, escape) { TEST(printf_test, escape) {
EXPECT_EQ("%", test_sprintf("%%")); EXPECT_EQ("%", test_sprintf("%%"));
@ -66,11 +53,6 @@ TEST(printf_test, escape) {
EXPECT_EQ("% after", test_sprintf("%% after")); EXPECT_EQ("% after", test_sprintf("%% after"));
EXPECT_EQ("before % after", test_sprintf("before %% after")); EXPECT_EQ("before % after", test_sprintf("before %% after"));
EXPECT_EQ("%s", test_sprintf("%%s")); EXPECT_EQ("%s", test_sprintf("%%s"));
EXPECT_EQ(L"%", fmt::sprintf(L"%%"));
EXPECT_EQ(L"before %", fmt::sprintf(L"before %%"));
EXPECT_EQ(L"% after", fmt::sprintf(L"%% after"));
EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after"));
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
} }
TEST(printf_test, positional_args) { TEST(printf_test, positional_args) {
@ -467,9 +449,6 @@ TEST(printf_test, char) {
EXPECT_PRINTF("x", "%c", 'x'); EXPECT_PRINTF("x", "%c", 'x');
int max = max_value<int>(); int max = max_value<int>();
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max); EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
// EXPECT_PRINTF("x", "%lc", L'x');
EXPECT_PRINTF(L"x", L"%c", L'x');
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
} }
TEST(printf_test, string) { TEST(printf_test, string) {
@ -477,10 +456,6 @@ TEST(printf_test, string) {
const char* null_str = nullptr; const char* null_str = nullptr;
EXPECT_PRINTF("(null)", "%s", null_str); EXPECT_PRINTF("(null)", "%s", null_str);
EXPECT_PRINTF(" (null)", "%10s", null_str); EXPECT_PRINTF(" (null)", "%10s", null_str);
EXPECT_PRINTF(L"abc", L"%s", L"abc");
const wchar_t* null_wstr = nullptr;
EXPECT_PRINTF(L"(null)", L"%s", null_wstr);
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
} }
TEST(printf_test, pointer) { TEST(printf_test, pointer) {
@ -494,16 +469,6 @@ TEST(printf_test, pointer) {
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s); EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
const char* null_str = nullptr; const char* null_str = nullptr;
EXPECT_PRINTF("(nil)", "%p", null_str); EXPECT_PRINTF("(nil)", "%p", null_str);
p = &n;
EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p);
p = nullptr;
EXPECT_PRINTF(L"(nil)", L"%p", p);
EXPECT_PRINTF(L" (nil)", L"%10p", p);
const wchar_t* w = L"test";
EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w);
const wchar_t* null_wstr = nullptr;
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
} }
enum test_enum { answer = 42 }; enum test_enum { answer = 42 };
@ -531,10 +496,6 @@ TEST(printf_test, printf_error) {
} }
#endif #endif
TEST(printf_test, wide_string) {
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
}
TEST(printf_test, vprintf) { TEST(printf_test, vprintf) {
int n = 42; int n = 42;
auto store = fmt::make_format_args<fmt::printf_context>(n); auto store = fmt::make_format_args<fmt::printf_context>(n);

View File

@ -28,11 +28,6 @@
# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY # define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
#endif #endif
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1910
# define FMT_RANGES_TEST_ENABLE_JOIN
# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
#endif
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY #ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
TEST(ranges_test, format_array) { TEST(ranges_test, format_array) {
int arr[] = {1, 2, 3, 5, 7, 11}; int arr[] = {1, 2, 3, 5, 7, 11};
@ -52,6 +47,8 @@ TEST(ranges_test, format_array_of_literals) {
} }
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY #endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
struct unformattable {};
TEST(ranges_test, format_vector) { TEST(ranges_test, format_vector) {
auto v = std::vector<int>{1, 2, 3, 5, 7, 11}; auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
@ -70,6 +67,9 @@ TEST(ranges_test, format_vector) {
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']"); EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'"); EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c"); EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
EXPECT_FALSE(fmt::is_formattable<unformattable>::value);
EXPECT_FALSE(fmt::is_formattable<std::vector<unformattable>>::value);
} }
TEST(ranges_test, format_nested_vector) { TEST(ranges_test, format_nested_vector) {
@ -88,6 +88,8 @@ TEST(ranges_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}}; auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2"); EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
EXPECT_FALSE((fmt::is_formattable<std::map<int, unformattable>>::value));
} }
struct test_map_value {}; struct test_map_value {};
@ -151,6 +153,7 @@ template <typename T> class flat_set {
TEST(ranges_test, format_flat_set) { TEST(ranges_test, format_flat_set) {
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}), EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
"{\"one\", \"two\"}"); "{\"one\", \"two\"}");
EXPECT_FALSE(fmt::is_formattable<flat_set<unformattable>>::value);
} }
namespace adl { namespace adl {
@ -170,19 +173,19 @@ TEST(ranges_test, format_adl_begin_end) {
TEST(ranges_test, format_pair) { TEST(ranges_test, format_pair) {
auto p = std::pair<int, float>(42, 1.5f); auto p = std::pair<int, float>(42, 1.5f);
EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)"); EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
EXPECT_EQ(fmt::format("{:}", p), "(42, 1.5)");
EXPECT_EQ(fmt::format("{:n}", p), "421.5");
} }
struct unformattable {};
TEST(ranges_test, format_tuple) { TEST(ranges_test, format_tuple) {
auto t = auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i'); std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')"); EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{:n}", t), "421.5\"this is tuple\"'i'");
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value)); EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value)); EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value)); EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value)); EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
@ -210,7 +213,6 @@ TEST(ranges_test, tuple_parse_calls_element_parse) {
EXPECT_THROW(f.parse(ctx), bad_format); EXPECT_THROW(f.parse(ctx), bad_format);
} }
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
struct tuple_like { struct tuple_like {
int i; int i;
std::string str; std::string str;
@ -230,9 +232,12 @@ auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
return t.get<N>(); return t.get<N>();
} }
// https://github.com/llvm/llvm-project/issues/39218
FMT_PRAGMA_CLANG(diagnostic ignored "-Wmismatched-tags")
namespace std { namespace std {
template <> template <>
struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {}; struct tuple_size<tuple_like> : public std::integral_constant<size_t, 2> {};
template <size_t N> struct tuple_element<N, tuple_like> { template <size_t N> struct tuple_element<N, tuple_like> {
using type = decltype(std::declval<tuple_like>().get<N>()); using type = decltype(std::declval<tuple_like>().get<N>());
@ -243,7 +248,6 @@ TEST(ranges_test, format_struct) {
auto t = tuple_like{42, "foo"}; auto t = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")"); EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
} }
#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
TEST(ranges_test, format_to) { TEST(ranges_test, format_to) {
char buf[10]; char buf[10];
@ -326,7 +330,7 @@ template <typename T> class noncopyable_range {
explicit noncopyable_range(Args&&... args) explicit noncopyable_range(Args&&... args)
: vec(std::forward<Args>(args)...) {} : vec(std::forward<Args>(args)...) {}
noncopyable_range(noncopyable_range const&) = delete; noncopyable_range(const noncopyable_range&) = delete;
noncopyable_range(noncopyable_range&) = delete; noncopyable_range(noncopyable_range&) = delete;
auto begin() -> iterator { return vec.begin(); } auto begin() -> iterator { return vec.begin(); }
@ -360,8 +364,7 @@ TEST(ranges_test, enum_range) {
#if !FMT_MSC_VERSION #if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) { TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>, EXPECT_FALSE((fmt::is_formattable<std::vector<unformattable>, char>::value));
fmt::format_context>::value));
} }
#endif #endif
@ -399,7 +402,6 @@ TEST(ranges_test, join_bytes) {
} }
#endif #endif
#ifdef FMT_RANGES_TEST_ENABLE_JOIN
TEST(ranges_test, join_tuple) { TEST(ranges_test, join_tuple) {
// Value tuple args. // Value tuple args.
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f); auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
@ -418,7 +420,11 @@ TEST(ranges_test, join_tuple) {
auto t4 = std::tuple<float>(4.0f); auto t4 = std::tuple<float>(4.0f);
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
# if FMT_TUPLE_JOIN_SPECIFIERS // Tuple-like.
auto t5 = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo");
#if FMT_TUPLE_JOIN_SPECIFIERS
// Specs applied to each element. // Specs applied to each element.
auto t5 = std::tuple<int, int, long>(-3, 100, 1); auto t5 = std::tuple<int, int, long>(-3, 100, 1);
EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01"); EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01");
@ -431,7 +437,7 @@ TEST(ranges_test, join_tuple) {
int y = -1; int y = -1;
auto t7 = std::tuple<int, int&, const int&>(3, y, y); auto t7 = std::tuple<int, int&, const int&>(3, y, y);
EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01"); EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01");
# endif #endif
} }
TEST(ranges_test, join_initializer_list) { TEST(ranges_test, join_initializer_list) {
@ -451,7 +457,7 @@ struct zstring {
auto end() const -> zstring_sentinel { return {}; } auto end() const -> zstring_sentinel { return {}; }
}; };
# ifdef __cpp_lib_ranges #ifdef __cpp_lib_ranges
struct cpp20_only_range { struct cpp20_only_range {
struct iterator { struct iterator {
int val = 0; int val = 0;
@ -481,7 +487,7 @@ struct cpp20_only_range {
}; };
static_assert(std::input_iterator<cpp20_only_range::iterator>); static_assert(std::input_iterator<cpp20_only_range::iterator>);
# endif #endif
TEST(ranges_test, join_sentinel) { TEST(ranges_test, join_sentinel) {
auto hello = zstring{"hello"}; auto hello = zstring{"hello"};
@ -509,13 +515,13 @@ TEST(ranges_test, join_range) {
const auto z = std::vector<int>(3u, 0); const auto z = std::vector<int>(3u, 0);
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0"); EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
# ifdef __cpp_lib_ranges #ifdef __cpp_lib_ranges
EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}), EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}),
"[0, 1, 2, 3, 4]"); "[0, 1, 2, 3, 4]");
EXPECT_EQ( EXPECT_EQ(
fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")), fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")),
"0,1,2,3,4"); "0,1,2,3,4");
# endif #endif
} }
namespace adl { namespace adl {
@ -531,8 +537,6 @@ TEST(ranges_test, format_join_adl_begin_end) {
EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43"); EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43");
} }
#endif // FMT_RANGES_TEST_ENABLE_JOIN
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L
TEST(ranges_test, nested_ranges) { TEST(ranges_test, nested_ranges) {
auto l = std::list{1, 2, 3}; auto l = std::list{1, 2, 3};
@ -558,7 +562,7 @@ TEST(ranges_test, escape) {
EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]"); EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]");
EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]"); EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]");
if (fmt::detail::use_utf8()) { if (fmt::detail::use_utf8) {
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]"); EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
// Unassigned Unicode code points. // Unassigned Unicode code points.
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]"); EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
@ -576,7 +580,11 @@ TEST(ranges_test, escape) {
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}), EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
"[['x']]"); "[['x']]");
// Disabled due to a clang 17 bug: https://github.com/fmtlib/fmt/issues/4144.
#if FMT_CLANG_VERSION >= 1800
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])"); EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
#endif
} }
template <typename R> struct fmt_ref_view { template <typename R> struct fmt_ref_view {
@ -655,6 +663,8 @@ TEST(ranges_test, container_adaptor) {
m.push(2); m.push(2);
EXPECT_EQ(fmt::format("{}", m), "[1, 2]"); EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
} }
EXPECT_FALSE(fmt::is_formattable<std::stack<unformattable>>::value);
} }
struct tieable { struct tieable {
@ -752,17 +762,17 @@ TEST(ranges_test, std_istream_iterator_join) {
EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", fmt::join(first, last, ", "))); EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", fmt::join(first, last, ", ")));
} }
TEST(ranges_test, movable_only_istream_iter_join) { // Mirrors C++20 std::ranges::basic_istream_view::iterator.
// Mirrors C++20 std::ranges::basic_istream_view::iterator. struct noncopyable_istream_iterator : std::istream_iterator<int> {
struct noncopyable_istream_iterator : std::istream_iterator<int> { using base = std::istream_iterator<int>;
explicit noncopyable_istream_iterator(std::istringstream& iss) explicit noncopyable_istream_iterator(std::istringstream& iss) : base{iss} {}
: std::istream_iterator<int>{iss} {} noncopyable_istream_iterator(const noncopyable_istream_iterator&) = delete;
noncopyable_istream_iterator(const noncopyable_istream_iterator&) = delete; noncopyable_istream_iterator(noncopyable_istream_iterator&&) = default;
noncopyable_istream_iterator(noncopyable_istream_iterator&&) = default; };
}; static_assert(!std::is_copy_constructible<noncopyable_istream_iterator>::value,
static_assert( "");
!std::is_copy_constructible<noncopyable_istream_iterator>::value, "");
TEST(ranges_test, movable_only_istream_iter_join) {
auto&& iss = std::istringstream("1 2 3 4 5"); auto&& iss = std::istringstream("1 2 3 4 5");
auto first = noncopyable_istream_iterator(iss); auto first = noncopyable_istream_iterator(iss);
auto last = std::istream_iterator<int>(); auto last = std::istream_iterator<int>();
@ -770,6 +780,18 @@ TEST(ranges_test, movable_only_istream_iter_join) {
fmt::format("{}", fmt::join(std::move(first), last, ", "))); fmt::format("{}", fmt::join(std::move(first), last, ", ")));
} }
struct movable_iter_range {
std::istringstream iss{"1 2 3 4 5"};
noncopyable_istream_iterator begin() {
return noncopyable_istream_iterator{iss};
}
std::istream_iterator<int> end() { return {}; }
};
TEST(ranges_test, movable_only_istream_iter_join2) {
EXPECT_EQ("[1, 2, 3, 4, 5]", fmt::format("{}", movable_iter_range{}));
}
struct not_range { struct not_range {
void begin() const {} void begin() const {}
void end() const {} void end() const {}

View File

@ -111,9 +111,9 @@ TEST(scan_test, invalid_format) {
} }
namespace std { namespace std {
using fmt::scan; using fmt::scan;
using fmt::scan_error; using fmt::scan_error;
} } // namespace std
TEST(scan_test, example) { TEST(scan_test, example) {
// Example from https://wg21.link/p1729r3. // Example from https://wg21.link/p1729r3.

View File

@ -192,7 +192,10 @@ class file_scan_buffer final : public scan_buffer {
flockfile(f); flockfile(f);
fill(); fill();
} }
~file_scan_buffer() { funlockfile(file_); } ~file_scan_buffer() {
FILE* f = file_;
funlockfile(f);
}
}; };
} // namespace detail } // namespace detail
@ -208,7 +211,7 @@ class scan_parse_context {
public: public:
using iterator = string_view::iterator; using iterator = string_view::iterator;
explicit FMT_CONSTEXPR scan_parse_context(string_view format) FMT_CONSTEXPR explicit scan_parse_context(string_view format)
: format_(format) {} : format_(format) {}
FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); } FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
@ -226,6 +229,8 @@ enum class scan_type {
uint_type, uint_type,
long_long_type, long_long_type,
ulong_long_type, ulong_long_type,
double_type,
float_type,
string_type, string_type,
string_view_type, string_view_type,
custom_type custom_type
@ -248,6 +253,8 @@ template <typename Context> class basic_scan_arg {
unsigned* uint_value_; unsigned* uint_value_;
long long* long_long_value_; long long* long_long_value_;
unsigned long long* ulong_long_value_; unsigned long long* ulong_long_value_;
double* double_value_;
float* float_value_;
std::string* string_; std::string* string_;
string_view* string_view_; string_view* string_view_;
detail::custom_scan_arg<Context> custom_; detail::custom_scan_arg<Context> custom_;
@ -273,6 +280,10 @@ template <typename Context> class basic_scan_arg {
: type_(scan_type::long_long_type), long_long_value_(&value) {} : type_(scan_type::long_long_type), long_long_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(unsigned long long& value) FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
: type_(scan_type::ulong_long_type), ulong_long_value_(&value) {} : type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(double& value)
: type_(scan_type::double_type), double_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(float& value)
: type_(scan_type::float_type), float_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(std::string& value) FMT_CONSTEXPR basic_scan_arg(std::string& value)
: type_(scan_type::string_type), string_(&value) {} : type_(scan_type::string_type), string_(&value) {}
FMT_CONSTEXPR basic_scan_arg(string_view& value) FMT_CONSTEXPR basic_scan_arg(string_view& value)
@ -302,6 +313,10 @@ template <typename Context> class basic_scan_arg {
return vis(*long_long_value_); return vis(*long_long_value_);
case scan_type::ulong_long_type: case scan_type::ulong_long_type:
return vis(*ulong_long_value_); return vis(*ulong_long_value_);
case scan_type::double_type:
return vis(*double_value_);
case scan_type::float_type:
return vis(*float_value_);
case scan_type::string_type: case scan_type::string_type:
return vis(*string_); return vis(*string_);
case scan_type::string_view_type: case scan_type::string_view_type:
@ -344,7 +359,7 @@ class scan_context {
using iterator = detail::scan_iterator; using iterator = detail::scan_iterator;
using sentinel = detail::scan_sentinel; using sentinel = detail::scan_sentinel;
explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args) FMT_CONSTEXPR explicit scan_context(detail::scan_buffer& buf, scan_args args)
: buf_(buf), args_(args) {} : buf_(buf), args_(args) {}
FMT_CONSTEXPR auto arg(int id) const -> scan_arg { FMT_CONSTEXPR auto arg(int id) const -> scan_arg {
@ -365,7 +380,7 @@ const char* parse_scan_specs(const char* begin, const char* end,
switch (to_ascii(*begin)) { switch (to_ascii(*begin)) {
// TODO: parse more scan format specifiers // TODO: parse more scan format specifiers
case 'x': case 'x':
specs.type = presentation_type::hex; specs.set_type(presentation_type::hex);
++begin; ++begin;
break; break;
case '}': case '}':
@ -434,7 +449,7 @@ auto read_hex(scan_iterator it, T& value) -> scan_iterator {
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
auto read(scan_iterator it, T& value, const format_specs& specs) auto read(scan_iterator it, T& value, const format_specs& specs)
-> scan_iterator { -> scan_iterator {
if (specs.type == presentation_type::hex) return read_hex(it, value); if (specs.type() == presentation_type::hex) return read_hex(it, value);
return read(it, value); return read(it, value);
} }
@ -454,6 +469,47 @@ auto read(scan_iterator it, T& value, const format_specs& specs = {})
return it; return it;
} }
auto read(scan_iterator it, double& value, const format_specs& = {})
-> scan_iterator {
if (it == scan_sentinel()) return it;
// Simple floating-point parsing
bool negative = *it == '-';
if (negative) {
++it;
if (it == scan_sentinel()) report_error("invalid input");
}
double result = 0.0;
// Parse integer part
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
result = result * 10.0 + (*it - '0');
++it;
}
// Parse decimal part if present
if (it != scan_sentinel() && *it == '.') {
++it;
double fraction = 0.1;
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
result += (*it - '0') * fraction;
fraction *= 0.1;
++it;
}
}
value = negative ? -result : result;
return it;
}
auto read(scan_iterator it, float& value, const format_specs& specs = {})
-> scan_iterator {
double temp;
it = read(it, temp, specs);
value = static_cast<float>(temp);
return it;
}
auto read(scan_iterator it, std::string& value, const format_specs& = {}) auto read(scan_iterator it, std::string& value, const format_specs& = {})
-> scan_iterator { -> scan_iterator {
while (it != scan_sentinel() && *it != ' ') value.push_back(*it++); while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);
@ -550,12 +606,12 @@ struct scan_handler {
return begin; return begin;
} }
void on_error(const char* message) { report_error(message); } FMT_NORETURN void on_error(const char* message) { report_error(message); }
}; };
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) { void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
auto h = detail::scan_handler(fmt, buf, args); auto h = detail::scan_handler(fmt, buf, args);
detail::parse_format_string<false>(fmt, h); detail::parse_format_string(fmt, h);
} }
template <size_t I, typename... T, FMT_ENABLE_IF(I == sizeof...(T))> template <size_t I, typename... T, FMT_ENABLE_IF(I == sizeof...(T))>

View File

@ -12,7 +12,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "fmt/os.h" // fmt::system_category #include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith #include "gtest-extra.h" // StartsWith
@ -20,6 +20,11 @@
TEST(std_test, path) { TEST(std_test, path) {
using std::filesystem::path; using std::filesystem::path;
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin"); EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
// see #4303
const path p = "/usr/bin";
EXPECT_EQ(fmt::format("{}", p), "/usr/bin");
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\""); EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo "); EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
@ -34,11 +39,18 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")), L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына"); "Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>"); EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\""); EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
"[\xED\xA0\xBD\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
# endif # endif
} }
// Intentionally delayed include to test #4303
# include "fmt/ranges.h"
// Test ambiguity problem described in #2954. // Test ambiguity problem described in #2954.
TEST(ranges_std_test, format_vector_path) { TEST(ranges_std_test, format_vector_path) {
auto p = std::filesystem::path("foo/bar.txt"); auto p = std::filesystem::path("foo/bar.txt");
@ -86,6 +98,9 @@ TEST(std_test, complex) {
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)"); EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)"); EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "(1+2i) ");
EXPECT_EQ(fmt::format("{:-<8}", std::complex<double>(1, 2)), "(1+2i)--");
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)), EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
" (1.00+2.20i)"); " (1.00+2.20i)");
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)), EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),
@ -130,11 +145,13 @@ TEST(std_test, optional) {
EXPECT_FALSE((fmt::is_formattable<unformattable>::value)); EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value)); EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value)); EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif #endif
} }
TEST(std_test, expected) { TEST(std_test, expected) {
#ifdef __cpp_lib_expected #ifdef __cpp_lib_expected
EXPECT_EQ(fmt::format("{}", std::expected<void, int>{}), "expected()");
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{1}), "expected(1)"); EXPECT_EQ(fmt::format("{}", std::expected<int, int>{1}), "expected(1)");
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{std::unexpected(1)}), EXPECT_EQ(fmt::format("{}", std::expected<int, int>{std::unexpected(1)}),
"unexpected(1)"); "unexpected(1)");
@ -158,6 +175,7 @@ TEST(std_test, expected) {
EXPECT_FALSE( EXPECT_FALSE(
(fmt::is_formattable<std::expected<int, unformattable2>>::value)); (fmt::is_formattable<std::expected<int, unformattable2>>::value));
EXPECT_TRUE((fmt::is_formattable<std::expected<int, int>>::value)); EXPECT_TRUE((fmt::is_formattable<std::expected<int, int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::expected<void, int>>::value));
#endif #endif
} }
@ -179,7 +197,33 @@ class my_class {
return fmt::to_string(elm.av); return fmt::to_string(elm.av);
} }
}; };
class my_class_int {
public:
int av;
private:
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
};
} // namespace my_nso } // namespace my_nso
TEST(std_test, expected_format_as) {
#ifdef __cpp_lib_expected
EXPECT_EQ(
fmt::format(
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
"expected(\"first\")");
EXPECT_EQ(
fmt::format("{}",
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
"expected(\"7\")");
EXPECT_EQ(fmt::format("{}",
std::expected<my_nso::my_class_int, int>{
my_nso::my_class_int{8}}),
"expected(8)");
#endif
}
TEST(std_test, optional_format_as) { TEST(std_test, optional_format_as) {
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
@ -188,6 +232,8 @@ TEST(std_test, optional_format_as) {
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}), EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
"optional(\"7\")"); "optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif #endif
} }
@ -257,16 +303,42 @@ TEST(std_test, variant) {
#endif #endif
} }
TEST(std_test, variant_format_as) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
"variant(\"first\")");
EXPECT_EQ(fmt::format(
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
"variant(\"first\")");
EXPECT_EQ(
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
"variant(\"7\")");
EXPECT_EQ(
fmt::format("{}",
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
"variant(8)");
#endif
}
TEST(std_test, error_code) { TEST(std_test, error_code) {
EXPECT_EQ("generic:42", auto& generic = std::generic_category();
fmt::format(FMT_STRING("{0}"), EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
std::error_code(42, std::generic_category()))); EXPECT_EQ(fmt::format("{:>12}", std::error_code(42, generic)),
EXPECT_EQ("system:42", " generic:42");
fmt::format(FMT_STRING("{0}"), EXPECT_EQ(fmt::format("{:12}", std::error_code(42, generic)), "generic:42 ");
std::error_code(42, fmt::system_category()))); EXPECT_EQ(fmt::format("{}", std::error_code(42, fmt::system_category())),
EXPECT_EQ("system:-42", "system:42");
fmt::format(FMT_STRING("{0}"), EXPECT_EQ(fmt::format("{}", std::error_code(-42, fmt::system_category())),
std::error_code(-42, fmt::system_category()))); "system:-42");
auto ec = std::make_error_code(std::errc::value_too_large);
EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
"\"generic:42\"");
EXPECT_EQ(fmt::format("{}",
std::map<std::error_code, int>{
{std::error_code(42, generic), 0}}),
"{\"generic:42\": 0}");
} }
template <typename Catch> void exception_test() { template <typename Catch> void exception_test() {
@ -362,11 +434,12 @@ TEST(std_test, format_atomic) {
#ifdef __cpp_lib_atomic_flag_test #ifdef __cpp_lib_atomic_flag_test
TEST(std_test, format_atomic_flag) { TEST(std_test, format_atomic_flag) {
std::atomic_flag f = ATOMIC_FLAG_INIT; std::atomic_flag f;
(void)f.test_and_set(); (void)f.test_and_set();
EXPECT_EQ(fmt::format("{}", f), "true"); EXPECT_EQ(fmt::format("{}", f), "true");
const std::atomic_flag cf = ATOMIC_FLAG_INIT; f.clear();
const std::atomic_flag& cf = f;
EXPECT_EQ(fmt::format("{}", cf), "false"); EXPECT_EQ(fmt::format("{}", cf), "false");
} }
#endif // __cpp_lib_atomic_flag_test #endif // __cpp_lib_atomic_flag_test
@ -388,3 +461,34 @@ TEST(std_test, format_shared_ptr) {
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())), EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
fmt::format("{}", fmt::ptr(sp))); fmt::format("{}", fmt::ptr(sp)));
} }
TEST(std_test, format_reference_wrapper) {
int num = 35;
EXPECT_EQ(fmt::to_string(std::cref(num)), "35");
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
}
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
struct type_with_format_as {};
int format_as(type_with_format_as) { return 20; }
TEST(std_test, format_reference_wrapper_with_format_as) {
type_with_format_as t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
}
struct type_with_format_as_string {};
std::string format_as(type_with_format_as_string) { return "foo"; }
TEST(std_test, format_reference_wrapper_with_format_as_string) {
type_with_format_as_string t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
}

View File

@ -12,7 +12,7 @@
void throw_assertion_failure(const char* message); void throw_assertion_failure(const char* message);
#define FMT_ASSERT(condition, message) \ #define FMT_ASSERT(condition, message) \
if (!(condition)) throw_assertion_failure(message); ((condition) ? (void)0 : throw_assertion_failure(message))
#include "gtest/gtest.h" #include "gtest/gtest.h"

View File

@ -15,7 +15,7 @@
using testing::Contains; using testing::Contains;
TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8()); } TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8); }
TEST(unicode_test, legacy_locale) { TEST(unicode_test, legacy_locale) {
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251"); auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");

View File

@ -36,12 +36,11 @@ std::locale do_get_locale(const char* name) {
std::locale get_locale(const char* name, const char* alt_name) { std::locale get_locale(const char* name, const char* alt_name) {
auto loc = do_get_locale(name); auto loc = do_get_locale(name);
if (loc == std::locale::classic() && alt_name) if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name);
loc = do_get_locale(alt_name);
#ifdef __OpenBSD__ #ifdef __OpenBSD__
// Locales are not working in OpenBSD: // Locales are not working in OpenBSD:
// https://github.com/fmtlib/fmt/issues/3670. // https://github.com/fmtlib/fmt/issues/3670.
loc = std::locale::classic(); loc = std::locale::classic();
#endif #endif
if (loc == std::locale::classic()) if (loc == std::locale::classic())
fmt::print(stderr, "{} locale is missing.\n", name); fmt::print(stderr, "{} locale is missing.\n", name);

View File

@ -31,17 +31,6 @@ extern const char* const file_content;
// Opens a buffered file for reading. // Opens a buffered file for reading.
auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file; auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file;
inline auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
template <typename Char> class basic_test_string { template <typename Char> class basic_test_string {
private: private:
std::basic_string<Char> value_; std::basic_string<Char> value_;

View File

@ -72,17 +72,18 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
#endif #endif
TEST(xchar_test, format) { TEST(xchar_test, format) {
EXPECT_EQ(L"42", fmt::format(L"{}", 42)); EXPECT_EQ(fmt::format(L"{}", 42), L"42");
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2");
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); EXPECT_EQ(fmt::format(L"{}", 1e100), L"1e+100");
EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc");
EXPECT_EQ(fmt::format(L"{}", L'z'), L"z");
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error); EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
EXPECT_EQ(L"true", fmt::format(L"{}", true)); EXPECT_EQ(fmt::format(L"{}", true), L"true");
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); EXPECT_EQ(fmt::format(L"{0}", L'a'), L"a");
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); EXPECT_EQ(fmt::format(L"Letter {}", L'\x40e'), L"Letter \x40e"); // Ў
EXPECT_EQ(L"Cyrillic letter \x42e", if (sizeof(wchar_t) == 4)
fmt::format(L"Cyrillic letter {}", L'\x42e')); EXPECT_EQ(fmt::format(fmt::runtime(L"{:𓀨>3}"), 42), L"𓀨42");
EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), L"abc1");
} }
TEST(xchar_test, is_formattable) { TEST(xchar_test, is_formattable) {
@ -96,93 +97,6 @@ TEST(xchar_test, compile_time_string) {
#endif #endif
} }
#if FMT_CPLUSPLUS > 201103L
struct custom_char {
int value;
custom_char() = default;
template <typename T>
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
constexpr operator char() const {
return value <= 0xff ? static_cast<char>(value) : '\0';
}
constexpr bool operator<(custom_char c) const { return value < c.value; }
};
namespace std {
template <> struct char_traits<custom_char> {
using char_type = custom_char;
using int_type = int;
using off_type = streamoff;
using pos_type = streampos;
using state_type = mbstate_t;
static constexpr void assign(char_type& r, const char_type& a) { r = a; }
static constexpr bool eq(char_type a, char_type b) { return a == b; }
static constexpr bool lt(char_type a, char_type b) { return a < b; }
static FMT_CONSTEXPR int compare(const char_type* s1, const char_type* s2,
size_t count) {
for (; count; count--, s1++, s2++) {
if (lt(*s1, *s2)) return -1;
if (lt(*s2, *s1)) return 1;
}
return 0;
}
static FMT_CONSTEXPR size_t length(const char_type* s) {
size_t count = 0;
while (!eq(*s++, custom_char(0))) count++;
return count;
}
static const char_type* find(const char_type*, size_t, const char_type&);
static FMT_CONSTEXPR char_type* move(char_type* dest, const char_type* src,
size_t count) {
if (count == 0) return dest;
char_type* ret = dest;
if (src < dest) {
dest += count;
src += count;
for (; count; count--) assign(*--dest, *--src);
} else if (src > dest)
copy(dest, src, count);
return ret;
}
static FMT_CONSTEXPR char_type* copy(char_type* dest, const char_type* src,
size_t count) {
char_type* ret = dest;
for (; count; count--) assign(*dest++, *src++);
return ret;
}
static FMT_CONSTEXPR char_type* assign(char_type* dest, std::size_t count,
char_type a) {
char_type* ret = dest;
for (; count; count--) assign(*dest++, a);
return ret;
}
static int_type not_eof(int_type);
static char_type to_char_type(int_type);
static int_type to_int_type(char_type);
static bool eq_int_type(int_type, int_type);
static int_type eof();
};
} // namespace std
auto to_ascii(custom_char c) -> char { return c; }
FMT_BEGIN_NAMESPACE
template <> struct is_char<custom_char> : std::true_type {};
FMT_END_NAMESPACE
TEST(xchar_test, format_custom_char) {
const custom_char format[] = {'{', '}', 0};
auto result = fmt::format(format, custom_char('x'));
EXPECT_EQ(result.size(), 1);
EXPECT_EQ(result[0], custom_char('x'));
}
#endif
TEST(xchar_test, format_to) { TEST(xchar_test, format_to) {
auto buf = std::vector<wchar_t>(); auto buf = std::vector<wchar_t>();
fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0'); fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
@ -232,7 +146,6 @@ TEST(format_test, wide_format_to_n) {
EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4)); EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
} }
#if FMT_USE_USER_DEFINED_LITERALS
TEST(xchar_test, named_arg_udl) { TEST(xchar_test, named_arg_udl) {
using namespace fmt::literals; using namespace fmt::literals;
auto udl_a = auto udl_a =
@ -243,7 +156,6 @@ TEST(xchar_test, named_arg_udl) {
fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)), fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
udl_a); udl_a);
} }
#endif // FMT_USE_USER_DEFINED_LITERALS
TEST(xchar_test, print) { TEST(xchar_test, print) {
// Check that the wide print overload compiles. // Check that the wide print overload compiles.
@ -260,6 +172,13 @@ TEST(xchar_test, join) {
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)"); EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
} }
#ifdef __cpp_lib_byte
TEST(xchar_test, join_bytes) {
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
EXPECT_EQ(fmt::format(L"{}", fmt::join(v, L", ")), L"1, 2, 3");
}
#endif
enum streamable_enum {}; enum streamable_enum {};
std::wostream& operator<<(std::wostream& os, streamable_enum) { std::wostream& operator<<(std::wostream& os, streamable_enum) {
@ -313,106 +232,9 @@ TEST(xchar_test, chrono) {
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25"); EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33"); EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
}
std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr, auto t = fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
std::locale* locptr = nullptr) { EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t), "1979-03-12 12:00:00");
auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
std::wostringstream os;
os.imbue(loc);
facet.put(os, os, L' ', timeptr, format.c_str(),
format.c_str() + format.size());
#ifdef _WIN32
// Workaround a bug in older versions of Universal CRT.
auto str = os.str();
if (str == L"-0000") str = L"+0000";
return str;
#else
return os.str();
#endif
}
TEST(chrono_test_wchar, time_point) {
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U",
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
#ifndef _WIN32
// Disabled on Windows, because these formats is not consistent among
// platforms.
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
#endif
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
// Timezone formatters tests makes sense for localtime.
#if FMT_HAS_C99_STRFTIME
spec_list = {L"%z", L"%Z"};
#else
spec_list = {L"%Z"};
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
if (spec == L"%z") {
sys_output.insert(sys_output.end() - 2, 1, L':');
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
}
}
// Separate tests for UTC, since std::time_put can use local time and ignoring
// the timezone in std::tm (if it presents on platform).
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
}
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
}
} }
TEST(xchar_test, color) { TEST(xchar_test, color) {
@ -557,7 +379,7 @@ TEST(locale_test, int_formatter) {
f.parse(parse_ctx); f.parse(parse_ctx);
auto buf = fmt::memory_buffer(); auto buf = fmt::memory_buffer();
fmt::basic_format_context<fmt::appender, char> format_ctx( fmt::basic_format_context<fmt::appender, char> format_ctx(
fmt::appender(buf), {}, fmt::detail::locale_ref(loc)); fmt::appender(buf), {}, fmt::locale_ref(loc));
f.format(12345, format_ctx); f.format(12345, format_ctx);
EXPECT_EQ(fmt::to_string(buf), "12,345"); EXPECT_EQ(fmt::to_string(buf), "12,345");
} }
@ -568,11 +390,9 @@ TEST(locale_test, chrono_weekday) {
auto sat = fmt::weekday(6); auto sat = fmt::weekday(6);
EXPECT_EQ(fmt::format(L"{}", sat), L"Sat"); EXPECT_EQ(fmt::format(L"{}", sat), L"Sat");
if (loc != std::locale::classic()) { if (loc != std::locale::classic()) {
// L'\xE1' is 'á'. // L'\341' is 'á'.
auto saturdays = std::vector<std::wstring>{ auto saturdays =
L"s\xE1" std::vector<std::wstring>{L"s\341b", L"s\341.", L"s\341b."};
"b",
L"s\xE1."};
EXPECT_THAT(saturdays, Contains(fmt::format(loc, L"{:L}", sat))); EXPECT_THAT(saturdays, Contains(fmt::format(loc, L"{:L}", sat)));
} }
std::locale::global(loc_old); std::locale::global(loc_old);
@ -582,11 +402,20 @@ TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50"); EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
} }
TEST(std_test_xchar, format_bitset) {
auto bs = std::bitset<6>(42);
EXPECT_EQ(fmt::format(L"{}", bs), L"101010");
EXPECT_EQ(fmt::format(L"{:0>8}", bs), L"00101010");
EXPECT_EQ(fmt::format(L"{:-^12}", bs), L"---101010---");
}
TEST(std_test_xchar, complex) { TEST(std_test_xchar, complex) {
auto s = fmt::format(L"{}", std::complex<double>(1, 2)); auto s = fmt::format(L"{}", std::complex<double>(1, 2));
EXPECT_EQ(s, L"(1+2i)"); EXPECT_EQ(s, L"(1+2i)");
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)), L"(1.00+2.00i)"); EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)),
L"(1.00+2.00i)");
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) "); EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) ");
EXPECT_EQ(fmt::format(L"{:-<8}", std::complex<double>(1, 2)), L"(1+2i)--");
} }
TEST(std_test_xchar, optional) { TEST(std_test_xchar, optional) {