Compare commits

..

98 Commits

Author SHA1 Message Date
Victor Zverovich
91d1aced0a Split exception into a separate file not to confuse openssf 2025-12-17 10:31:12 -08:00
user202729
2e819a11f2 Some documentation improvements (#4631) 2025-12-17 06:53:01 -08:00
Zephyr Lykos
7bce22571a Make it work on newer mkdocstrings, fix deprecation warnings (#4626)
* Make it work on newer mkdocstrings, fix deprecation warnings

* Bump documentation dependency versions
2025-12-14 08:36:41 -08:00
Victor Zverovich
ec73fb7247 Cleanup gtest cmake config 2025-12-09 18:27:32 -08:00
Victor Zverovich
3269c1cea5 Minor improvement to digits2 2025-12-09 18:27:06 -08:00
marcel-behlau-elfin
789aa69e0a fix: add missing operator (#4627) 2025-12-09 12:38:28 -08:00
Zephyr Lykos
2a50a0d808 Remove unused symbol check (#4625)
Fixes: 56d7a8c157
Fixes: a3bf40838f
2025-12-08 12:23:37 -08:00
Victor Zverovich
02d6f3d9e4 Bump version 2025-12-07 08:20:47 -08:00
Victor Zverovich
19e4d5ecd9 Fix handling of pointers in format string compilation and FMT_BUILTIN_TYPES=0 2025-12-07 08:07:09 -08:00
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
38 changed files with 1501 additions and 1156 deletions

View File

@@ -25,7 +25,7 @@ jobs:
language: c++
- name: Upload crash
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Add Ubuntu mirrors
run: |
@@ -24,7 +24,7 @@ jobs:
run: |
sudo apt update
sudo apt install doxygen
pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
pip install mkdocs-material==9.7.0 mkdocstrings==1.0.0 mike==2.1.3 typing_extensions==4.15.0
cmake -E make_directory ${{runner.workspace}}/build
# Workaround https://github.com/actions/checkout/issues/13:
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"

View File

@@ -13,16 +13,16 @@ jobs:
format_code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install clang-format
run: |
wget https://apt.llvm.org/llvm.sh
sudo bash ./llvm.sh 17
sudo apt install clang-format-17
sudo bash ./llvm.sh 21
sudo apt install clang-format-21
- name: Run clang-format
run: |
find include src -name '*.h' -o -name '*.cc' | \
xargs clang-format-17 -i -style=file -fallback-style=none
xargs clang-format-21 -i -style=file -fallback-style=none
git diff --exit-code

View File

@@ -55,7 +55,7 @@ jobs:
install: sudo apt install libc++-14-dev libc++abi-14-dev
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone
run: sudo timedatectl set-timezone 'Europe/Kyiv'

View File

@@ -9,13 +9,10 @@ jobs:
build:
strategy:
matrix:
os: [macos-13, macos-14]
os: [macos-14]
build_type: [Debug, Release]
std: [11, 17, 20]
std: [11, 17, 20, 23]
shared: [""]
exclude:
- { os: macos-13, std: 11 }
- { os: macos-13, std: 17 }
include:
- os: macos-14
std: 23
@@ -25,7 +22,7 @@ jobs:
runs-on: '${{ matrix.os }}'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone
run: sudo systemsetup -settimezone 'Europe/Minsk'

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
@@ -52,7 +52,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: SARIF file
path: results.sarif
@@ -60,6 +60,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
with:
sarif_file: results.sarif

View File

@@ -32,7 +32,7 @@ jobs:
standard: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone
run: tzutil /s "FLE Standard Time"
@@ -79,7 +79,7 @@ jobs:
release: false
msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Configure
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
env: { LDFLAGS: -fuse-ld=lld }

View File

@@ -1,3 +1,77 @@
# 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

View File

@@ -18,10 +18,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.

View File

@@ -12,7 +12,7 @@
alternative to C stdio and C++ iostreams.
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)
@@ -150,8 +150,8 @@ int main() {
}
```
This can be [5 to 9 times faster than
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
This can be [up to 9 times faster than `fprintf`](
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles**
@@ -178,17 +178,17 @@ Output on a modern terminal with Unicode support:
| Library | Method | Run Time, s |
|-------------------|---------------|-------------|
| libc | printf | 0.91 |
| libc++ | std::ostream | 2.49 |
| {fmt} 9.1 | fmt::print | 0.74 |
| Boost Format 1.80 | boost::format | 6.26 |
| Folly Format | folly::format | 1.87 |
| libc | printf | 0.66 |
| libc++ | std::ostream | 1.63 |
| {fmt} 12.1 | fmt::print | 0.44 |
| Boost Format 1.88 | boost::format | 3.89 |
| 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`.
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
`"%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
@@ -216,26 +216,26 @@ in the following tables.
**Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 |
| IOStreams | 25.9 | 98 | 84 |
| fmt 83652df | 4.8 | 54 | 50 |
| tinyformat | 29.1 | 161 | 136 |
| Boost Format | 55.0 | 530 | 317 |
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 |
| IOStreams | 28.4 | 98 | 84 |
| {fmt} `1122268` | 5.0 | 54 | 50 |
| tinyformat | 32.6 | 164 | 136 |
| Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
binary size (within a rounding error on this system).
**Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 |
| IOStreams | 23.4 | 92 | 68 |
| {fmt} 83652df | 4.4 | 89 | 85 |
| tinyformat | 24.5 | 204 | 161 |
| Boost Format | 36.4 | 831 | 462 |
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 |
| IOStreams | 27.0 | 88 | 68 |
| {fmt} `1122268` | 4.7 | 87 | 84 |
| tinyformat | 28.1 | 185 | 145 |
| Boost Format | 38.9 | 678 | 381 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a

27
doc/LICENSE-exception Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.

View File

@@ -321,8 +321,6 @@ parameterized version.
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Compatibility
::: basic_string_view
@@ -624,6 +622,8 @@ Example:
::: ostream
::: output_file(cstring_view, T...)
::: windows_error
<a id="ostream-api"></a>
@@ -706,5 +706,55 @@ following differences:
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 procude more
(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;
}
code,
pre > code.decl {
white-space: pre-wrap;
}

View File

@@ -76,7 +76,7 @@ hide:
<p>
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
standard libary.
standard library.
</p>
</div>

View File

@@ -251,7 +251,7 @@ The available integer presentation types are:
<td><code>'b'</code></td>
<td>
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>
</tr>
<tr>
@@ -718,7 +718,7 @@ These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
Format specifications for range types have the following syntax:
<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>
The `'n'` option formats the range without the opening and closing brackets.
@@ -761,14 +761,16 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [h, e, l, l, o]
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// 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
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
example, `"%03.2f"` can be translated to `"{:03.2f}"`.

View File

@@ -21,7 +21,7 @@
#endif
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 120000
#define FMT_VERSION 120101
// Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__)
@@ -114,7 +114,9 @@
#endif
// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated.
#if !defined(__cpp_lib_is_constant_evaluated)
#ifdef FMT_USE_CONSTEVAL
// Use the provided definition.
#elif !defined(__cpp_lib_is_constant_evaluated)
# define FMT_USE_CONSTEVAL 0
#elif FMT_CPLUSPLUS < 201709L
# define FMT_USE_CONSTEVAL 0
@@ -231,9 +233,9 @@
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og"))
# define FMT_GCC_OPTIMIZED
#endif
FMT_PRAGMA_CLANG(diagnostic push)
FMT_PRAGMA_GCC(diagnostic push)
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
@@ -243,7 +245,7 @@ FMT_PRAGMA_CLANG(diagnostic push)
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
#ifdef NDEBUG
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
@@ -414,8 +416,12 @@ inline auto map(int128_opt) -> monostate { return {}; }
inline auto map(uint128_opt) -> monostate { return {}; }
#endif
#ifndef FMT_USE_BITINT
# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500)
#ifdef FMT_USE_BITINT
// Use the provided definition.
#elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__)
# define FMT_USE_BITINT 1
#else
# define FMT_USE_BITINT 0
#endif
#if FMT_USE_BITINT
@@ -918,9 +924,15 @@ class locale_ref {
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc);
locale_ref(const Locale& loc) : locale_(&loc) {
// Check if std::isalpha is found via ADL to reduce the chance of misuse.
detail::ignore_unused(sizeof(isalpha('x', loc)));
}
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#else
public:
inline explicit operator bool() const noexcept { return false; }
#endif // FMT_USE_LOCALE
public:
@@ -1841,15 +1853,19 @@ template <typename T> class buffer {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940
FMT_CONSTEXPR20
#endif
void
append(const U* begin, const U* end) {
void append(const U* begin, const U* end) {
while (begin != end) {
auto size = size_;
auto free_cap = capacity_ - size;
auto count = to_unsigned(end - begin);
try_reserve(size_ + count);
auto free_cap = capacity_ - size_;
if (free_cap < count) count = free_cap;
if (free_cap < count) {
grow_(*this, size + count);
size = size_;
free_cap = capacity_ - size;
count = count < free_cap ? count : free_cap;
}
// A loop is faster than memcpy on small sizes.
T* out = ptr_ + size_;
T* out = ptr_ + size;
for (size_t i = 0; i < count; ++i) out[i] = begin[i];
size_ += count;
begin += count;
@@ -2245,8 +2261,11 @@ template <typename Context> class value {
: pointer(const_cast<const void*>(x)) {}
FMT_INLINE value(nullptr_t) : pointer(nullptr) {}
template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value ||
std::is_member_pointer<T>::value)>
template <typename T,
FMT_ENABLE_IF(
(std::is_pointer<T>::value ||
std::is_member_pointer<T>::value) &&
!std::is_void<typename std::remove_pointer<T>::type>::value)>
value(const T&) {
// Formatting of arbitrary pointers is disallowed. If you want to format a
// pointer cast it to `void*` or `const void*`. In particular, this forbids
@@ -2291,7 +2310,7 @@ template <typename Context> class value {
template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>
FMT_CONSTEXPR value(const T&, custom_tag) {
// Cannot format an argument; to make type T formattable provide a
// formatter<T> specialization: https://fmt.dev/latest/api.html#udt.
// formatter<T> specialization: https://fmt.dev/latest/api#udt.
type_is_unformattable_for<T, char_type> _;
}
@@ -2741,7 +2760,9 @@ template <typename... T> struct fstring {
static_assert(count<(is_view<remove_cvref_t<T>>::value &&
std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed");
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
#if FMT_USE_CONSTEVAL
parse_format_string<char>(s, checker(s, arg_pack()));
#endif
#ifdef FMT_ENFORCE_COMPILE_STRING
static_assert(
FMT_USE_CONSTEVAL && sizeof(s) != 0,
@@ -2826,6 +2847,10 @@ using vargs =
* **Example**:
*
* fmt::print("The answer is {answer}.", fmt::arg("answer", 42));
*
* Named arguments passed with `fmt::arg` are not supported
* in compile-time checks, but `"answer"_a=42` are compile-time checked in
* sufficiently new compilers. See `operator""_a()`.
*/
template <typename Char, typename T>
inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {
@@ -2983,6 +3008,7 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
return fmt::println(stdout, fmt, static_cast<T&&>(args)...);
}
FMT_PRAGMA_GCC(diagnostic pop)
FMT_PRAGMA_CLANG(diagnostic pop)
FMT_PRAGMA_GCC(pop_options)
FMT_END_EXPORT

View File

@@ -1594,8 +1594,13 @@ class get_locale {
public:
inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
if (localized)
::new (&locale_) std::locale(loc.template get<std::locale>());
if (!localized) return;
ignore_unused(loc);
::new (&locale_) std::locale(
#if FMT_USE_LOCALE
loc.template get<std::locale>()
#endif
);
}
inline ~get_locale() {
if (has_locale_) locale_.~locale();

View File

@@ -155,7 +155,7 @@ enum class color : uint32_t {
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
@@ -429,7 +429,7 @@ template <typename Char> struct ansi_color_escape {
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases];
Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,

View File

@@ -15,9 +15,10 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
// A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {};
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
@@ -59,6 +60,8 @@ template <detail::fixed_string Str> constexpr auto operator""_cf() {
} // namespace literals
#endif
FMT_END_EXPORT
namespace detail {
template <typename T, typename... Tail>
@@ -519,7 +522,7 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()};
}
@@ -556,8 +559,8 @@ template <size_t N> class static_format_result {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
auto str() const -> fmt::string_view { return {data, N - 1}; }
auto c_str() const -> const char* { return data; }
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
};
/**

File diff suppressed because it is too large Load Diff

View File

@@ -40,11 +40,18 @@
#include "base.h"
// libc++ supports string_view in pre-c++17.
#if FMT_HAS_INCLUDE(<string_view>) && \
(FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION))
# define FMT_USE_STRING_VIEW
#endif
#ifndef FMT_MODULE
# include <stdlib.h> // malloc, free
# include <cmath> // std::signbit
# include <cstddef> // std::byte
# include <cstdint> // uint32_t
# include <cstdlib> // std::malloc, std::free
# include <cstring> // std::memcpy
# include <limits> // std::numeric_limits
# include <new> // std::bad_alloc
@@ -61,11 +68,8 @@
# include <bit> // std::bit_cast
# endif
// libc++ supports string_view in pre-c++17.
# if FMT_HAS_INCLUDE(<string_view>) && \
(FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION))
# if defined(FMT_USE_STRING_VIEW)
# include <string_view>
# define FMT_USE_STRING_VIEW
# endif
# if FMT_MSC_VERSION
@@ -489,8 +493,8 @@ template <typename OutputIt,
#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
__attribute__((no_sanitize("undefined")))
#endif
FMT_CONSTEXPR20 inline auto
reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* {
FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) ->
typename OutputIt::value_type* {
auto& c = get_container(it);
size_t size = c.size();
c.resize(size + n);
@@ -732,24 +736,20 @@ using fast_float_t = conditional_t<sizeof(T) == sizeof(double), double, float>;
template <typename T>
using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
#endif
// An allocator that uses malloc/free to allow removing dependency on the C++
// standard libary runtime. std::decay is used for back_inserter to be found by
// standard library runtime. std::decay is used for back_inserter to be found by
// ADL when applied to memory_buffer.
template <typename T> struct allocator : private std::decay<void> {
using value_type = T;
auto allocate(size_t n) -> T* {
FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), "");
T* p = static_cast<T*>(std::malloc(n * sizeof(T)));
T* p = static_cast<T*>(malloc(n * sizeof(T)));
if (!p) FMT_THROW(std::bad_alloc());
return p;
}
void deallocate(T* p, size_t) { std::free(p); }
void deallocate(T* p, size_t) { free(p); }
constexpr friend auto operator==(allocator, allocator) noexcept -> bool {
return true; // All instances of this allocator are equivalent.
@@ -759,6 +759,14 @@ template <typename T> struct allocator : private std::decay<void> {
}
};
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&, ...) {}
} // namespace detail
FMT_BEGIN_EXPORT
@@ -1019,9 +1027,9 @@ using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
(factor) * 100000, (factor) * 1000000, (factor) * 10000000, \
(factor) * 100000000, (factor) * 1000000000
// Converts value in the range [0, 100) to a string.
// GCC generates slightly better code when value is pointer-size.
inline auto digits2(size_t value) -> const char* {
// Converts value in the range [0, 100) to a string. GCC generates a bit better
// code when value is pointer-size (https://www.godbolt.org/z/5fEPMT1cc).
inline auto digits2(size_t value) noexcept -> const char* {
// Align data since unaligned access may be slower when crossing a
// hardware-specific boundary.
alignas(2) static const char data[] =
@@ -1299,7 +1307,13 @@ class utf8_to_utf16 {
inline auto str() const -> std::wstring { return {&buffer_[0], size()}; }
};
enum class to_utf8_error_policy { abort, replace };
enum class to_utf8_error_policy { abort, replace, wtf };
inline void to_utf8_3bytes(buffer<char>& buf, uint32_t cp) {
buf.push_back(static_cast<char>(0xe0 | (cp >> 12)));
buf.push_back(static_cast<char>(0x80 | ((cp & 0xfff) >> 6)));
buf.push_back(static_cast<char>(0x80 | (cp & 0x3f)));
}
// A converter from UTF-16/UTF-32 (host endian) to UTF-8.
template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
@@ -1341,8 +1355,13 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
// Handle a surrogate pair.
++p;
if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
if (policy == to_utf8_error_policy::abort) return false;
buf.append(string_view("\xEF\xBF\xBD"));
switch (policy) {
case to_utf8_error_policy::abort: return false;
case to_utf8_error_policy::replace:
buf.append(string_view("\xEF\xBF\xBD"));
break;
case to_utf8_error_policy::wtf: to_utf8_3bytes(buf, c); break;
}
--p;
continue;
}
@@ -1354,9 +1373,7 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
buf.push_back(static_cast<char>(0xc0 | (c >> 6)));
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
buf.push_back(static_cast<char>(0xe0 | (c >> 12)));
buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
to_utf8_3bytes(buf, c);
} else if (c >= 0x10000 && c <= 0x10ffff) {
buf.push_back(static_cast<char>(0xf0 | (c >> 18)));
buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
@@ -2506,7 +2523,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp);
return write_padded<Char, align::right>(
out, specs, to_unsigned(size), [&](iterator it) {
out, specs, static_cast<size_t>(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand<Char>(it, f.significand, significand_size,
f.exponent, grouping);
@@ -2522,7 +2539,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp);
return write_padded<Char, align::right>(
out, specs, to_unsigned(size), [&](iterator it) {
out, specs, static_cast<size_t>(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand(it, f.significand, significand_size, exp,
decimal_point, grouping);
@@ -2538,7 +2555,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
size += 1 + (pointy ? 1 : 0) + num_zeros;
return write_padded<Char, align::right>(
out, specs, to_unsigned(size), [&](iterator it) {
out, specs, static_cast<size_t>(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
*it++ = Char('0');
if (!pointy) return it;
@@ -2582,7 +2599,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
*it++ = Char(exp_char);
return write_exponent<Char>(exp, it);
};
auto usize = to_unsigned(size);
size_t usize = static_cast<size_t>(size);
return specs.width > 0
? write_padded<Char, align::right>(out, specs, usize, write)
: base_iterator(out, write(reserve(out, usize)));
@@ -4130,6 +4147,14 @@ template <typename T, typename Char = char> struct nested_formatter {
inline namespace literals {
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
/**
* User-defined literal equivalent of `fmt::arg`, but with compile-time checks.
*
* **Example**:
*
* using namespace fmt::literals;
* fmt::print("The answer is {answer}.", "answer"_a=42);
*/
template <detail::fixed_string S> constexpr auto operator""_a() {
using char_t = remove_cvref_t<decltype(*S.data)>;
return detail::udl_arg<char_t, sizeof(S.data) / sizeof(char_t), S>();
@@ -4236,7 +4261,11 @@ class format_int {
* // A compile-time error because 'd' is an invalid specifier for strings.
* std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)
#if FMT_USE_CONSTEVAL
# define FMT_STRING(s) s
#else
# define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)
#endif // FMT_USE_CONSTEVAL
FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args)
-> std::system_error;

View File

@@ -136,10 +136,9 @@ FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
* **Example**:
*
* // This throws a system_error with the description
* // cannot open file 'madeup': The system cannot find the file
* specified.
* // or similar (system message may vary).
* const char *filename = "madeup";
* // cannot open file 'foo': The system cannot find the file specified.
* // or similar (system message may vary) if the file doesn't exist.
* const char *filename = "foo";
* LPOFSTRUCT of = LPOFSTRUCT();
* HFILE file = OpenFile(filename, &of, OF_READ);
* if (file == HFILE_ERROR) {
@@ -162,14 +161,6 @@ inline auto system_category() noexcept -> const std::error_category& {
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
@@ -365,17 +356,17 @@ 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 FMT_API ostream : private detail::buffer<char> {
class ostream : private detail::buffer<char> {
private:
file file_;
ostream(cstring_view path, const detail::ostream_params& params);
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
static void grow(buffer<char>& buf, size_t);
FMT_API static void grow(buffer<char>& buf, size_t);
public:
ostream(ostream&& other) noexcept;
~ostream();
FMT_API ostream(ostream&& other) noexcept;
FMT_API ~ostream();
operator writer() {
detail::buffer<char>& buf = *this;

View File

@@ -38,7 +38,7 @@ namespace detail {
namespace {
struct file_access_tag {};
} // namespace
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
template <typename Tag, typename BufType, FILE* BufType::* FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};

View File

@@ -18,6 +18,13 @@
#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_EXPORT
@@ -234,14 +241,6 @@ using range_reference_type =
template <typename 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>
struct range_format_kind_
: std::integral_constant<range_format,
@@ -821,12 +820,12 @@ auto join(Range&& r, string_view sep)
*
* **Example**:
*
* auto t = std::tuple<int, char>{1, 'a'};
* auto t = std::tuple<int, char>(1, 'a');
* fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a
*/
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep)
-> tuple_join_view<Tuple, char> {
return {tuple, sep};
}

View File

@@ -84,10 +84,12 @@ namespace detail {
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf);
} else {
return p.string<Char>();
}
}
template <typename Char, typename PathChar>
@@ -111,12 +113,17 @@ 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>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
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);
return write<Char>(out, v);
formatter<std::remove_cv_t<T>, Char> underlying;
maybe_set_debug_format(underlying, true);
return underlying.format(v, ctx);
}
#endif
@@ -139,50 +146,39 @@ template <typename Variant, typename Char> class is_variant_formattable {
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
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), &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;
}
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)};
*to++ = *from++;
}
} else {
demangled_name_view = string_view(ti.name());
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
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);
@@ -201,6 +197,39 @@ auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
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
@@ -255,21 +284,6 @@ template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
#if FMT_CPP_LIB_FILESYSTEM
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs specs_;
@@ -319,6 +333,21 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
}
};
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
#endif // FMT_CPP_LIB_FILESYSTEM
template <size_t N, typename Char>
@@ -353,25 +382,16 @@ template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
formatter<std::remove_cv_t<T>, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
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);
}
@@ -407,10 +427,10 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) {
out = detail::write<Char>(out, "expected(");
if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value);
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
} else {
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++ = ')';
return out;
@@ -474,7 +494,7 @@ struct formatter<Variant, Char,
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v);
out = detail::write_escaped_alternative<Char>(out, v, ctx);
},
value);
}
@@ -495,6 +515,8 @@ template <> struct formatter<std::error_code> {
bool debug_ = false;
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;
@@ -625,6 +647,11 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
};
#endif // __cpp_lib_atomic_flag_test
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> {
private:
detail::dynamic_format_specs<Char> specs_;

View File

@@ -10,7 +10,7 @@
FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc);
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

View File

@@ -2,6 +2,10 @@
# A script to invoke mkdocs with the correct environment.
# Additionally supports deploying via mike:
# ./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
from subprocess import call
@@ -40,7 +44,7 @@ config_path = os.path.join(support_dir, 'mkdocs.yml')
args = sys.argv[1:]
if len(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:'
site_repo = git_url + 'fmtlib/fmt.dev.git'
@@ -64,14 +68,18 @@ if len(args) > 0:
if ret != 0 or version == 'dev':
sys.exit(ret)
current_doc_path = os.path.join(site_dir, version)
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)
# 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('-'):
args += ['-f', config_path]

View File

@@ -2,190 +2,274 @@
# Copyright (c) 2012 - present, Victor Zverovich
# https://github.com/fmtlib/fmt/blob/master/LICENSE
# pyright: strict
import os
import xml.etree.ElementTree as ElementTree
import xml.etree.ElementTree as ET
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
from typing import Any, List, Mapping, Optional
from mkdocstrings.handlers.base import BaseHandler
from markupsafe import Markup
from mkdocstrings import BaseHandler
from typing_extensions import TYPE_CHECKING, Any, ClassVar, final, override
if TYPE_CHECKING:
from collections.abc import Mapping, MutableMapping
from mkdocs.config.defaults import MkDocsConfig
from mkdocstrings import CollectorItem, HandlerOptions
@final
class Definition:
"""A definition extracted by Doxygen."""
def __init__(self, name: str, kind: Optional[str] = None,
node: Optional[ElementTree.Element] = None,
is_member: bool = False):
def __init__(
self,
name: str,
kind: "str | None" = None,
node: "ET.Element | None" = None,
is_member: bool = False,
):
self.name = name
self.kind = kind if kind is not None else node.get('kind')
self.desc = 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
self.kind: "str | None" = None
if kind is not None:
self.kind = kind
elif node is not None:
self.kind = node.get("kind")
self.desc: "list[ET.Element[str]] | None" = None
self.id: "str | None" = name if not is_member else None
self.members: "list[Definition] | None" = None
self.params: "list[Definition] | None" = None
self.template_params: "list[Definition] | None" = None
self.trailing_return_type: "str | None" = None
self.type: "str | None" = None
# A map from Doxygen to HTML tags.
tag_map = {
'bold': 'b',
'emphasis': 'em',
'computeroutput': 'code',
'para': 'p',
'programlisting': 'pre',
'verbatim': 'pre'
"bold": "b",
"emphasis": "em",
"computeroutput": "code",
"para": "p",
"itemizedlist": "ul",
"listitem": "li",
}
# A map from Doxygen tags to text.
tag_text_map = {
'codeline': '',
'highlight': '',
'sp': ' '
}
tag_text_map = {"codeline": "", "highlight": "", "sp": " "}
def escape_html(s: str) -> str:
return s.replace("<", "&lt;")
def doxyxml2html(nodes: List[ElementTree.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(list(n))
out += '</code>' if tag == 'pre' else ''
out += '</' + tag + '>' if tag else ''
if n.tail:
out += n.tail
# Converts a node from doxygen to HTML format.
def convert_node(
node: ET.Element, tag: str, attrs: "Mapping[str, str] | None" = None
) -> str:
if attrs is None:
attrs = {}
out: str = "<" + tag
for key, value in attrs.items():
out += " " + key + '="' + value + '"'
out += ">"
if node.text:
out += escape_html(node.text)
out += doxyxml2html(list(node))
out += "</" + tag + ">"
if node.tail:
out += node.tail
return out
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
template_param_list = node.find('templateparamlist')
def doxyxml2html(nodes: "list[ET.Element]"):
out = ""
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_template_params(node: ET.Element) -> "list[Definition] | None":
template_param_list = node.find("templateparamlist")
if template_param_list is None:
return None
params = []
for param_node in template_param_list.findall('param'):
name = param_node.find('declname')
param = Definition(name.text if name is not None else '', 'param')
param.type = param_node.find('type').text
params: "list[Definition]" = []
for param_node in template_param_list.findall("param"):
name = param_node.find("declname")
if name is not None:
name = name.text
if name is None:
name = ""
param = Definition(name, "param")
param_type = param_node.find("type")
if param_type is not None:
param.type = param_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 get_description(node: ET.Element) -> list[ET.Element]:
return node.findall("briefdescription/para") + node.findall(
"detaileddescription/para"
)
def normalize_type(type_: str) -> str:
type_ = type_.replace('< ', '<').replace(' >', '>')
return type_.replace(' &', '&').replace(' *', '*')
type_ = type_.replace("< ", "<").replace(" >", ">")
return type_.replace(" &", "&").replace(" *", "*")
def convert_type(type_: ElementTree.Element) -> Optional[str]:
def convert_type(type_: "ET.Element | None") -> "str | None":
if type_ is None:
return None
result = type_.text if type_.text else ''
result = type_.text if type_.text else ""
for ref in type_:
if ref.text is None:
raise ValueError
result += ref.text
if ref.tail:
result += ref.tail
if type_.tail is None:
raise ValueError
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'))
def convert_params(func: ET.Element) -> list[Definition]:
params: "list[Definition]" = []
for p in func.findall("param"):
declname = p.find("declname")
if declname is None or declname.text is None:
raise ValueError
d = Definition(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:
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 d.type == "auto" or d.type == "constexpr auto":
argsstring = node.find("argsstring")
if argsstring is None or argsstring.text is None:
raise ValueError
parts = argsstring.text.split(" -> ")
if len(parts) > 1:
d.trailing_return_type = normalize_type(parts[1])
def render_param(param: Definition) -> str:
return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '')
if param.type is None:
raise ValueError
return param.type + (f"&nbsp;{param.name}" if len(param.name) > 0 else "")
def render_decl(d: Definition) -> str:
text = ''
text = ""
if d.id is not None:
text += f'<a id="{d.id}">\n'
text += '<pre><code class="language-cpp decl">'
text += '<div>'
text += "<div>"
if d.template_params is not None:
text += 'template &lt;'
text += ', '.join([render_param(p) for p in d.template_params])
text += '&gt;\n'
text += '</div>'
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 = ''
text += "<div>"
end = ";"
if d.kind is None:
raise ValueError
if d.kind == "function" or d.kind == "variable":
if d.type is None:
raise ValueError
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.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) + ')'
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 += " -&NoBreak;>&nbsp;" + escape_html(d.trailing_return_type)
elif d.kind == "typedef":
if d.type is None:
raise ValueError
text += " = " + escape_html(d.type)
text += end
text += '</div>'
text += '</code></pre>\n'
text += "</div>"
text += "</code></pre>\n"
if d.id is not None:
text += f'</a>\n'
text += "</a>\n"
return text
@final
class CxxHandler(BaseHandler):
def __init__(self, **kwargs: Any) -> None:
super().__init__(handler='cxx', **kwargs)
name: ClassVar[str] = "cxx"
domain: ClassVar[str] = "cxx"
def __init__(
self, config: "Mapping[str, Any]", base_dir: Path, **kwargs: Any
) -> None:
super().__init__(**kwargs)
self.config = config
"""The handler configuration."""
self.base_dir = base_dir
"""The base directory of the project."""
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'
"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', '-']
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')
include_dir = os.path.join(top_dir, "include", "fmt")
self._ns2doxyxml: "dict[str, ET.ElementTree[ET.Element[str]]]" = {}
build_dir = os.path.join(top_dir, "build")
os.makedirs(build_dir, exist_ok=True)
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
self._doxyxml_dir = os.path.join(build_dir, "doxyxml")
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
_, _ = p.communicate(input=r'''
_, _ = p.communicate(
input=r"""
PROJECT_NAME = fmt
GENERATE_XML = YES
GENERATE_LATEX = NO
@@ -205,18 +289,20 @@ class CxxHandler(BaseHandler):
"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'))
""".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
self._file_doxyxml: "ET.ElementTree[ET.Element[str]] | None" = 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)
doxyxml = ET.parse(f)
if self._file_doxyxml is None:
self._file_doxyxml = doxyxml
continue
@@ -224,33 +310,43 @@ class CxxHandler(BaseHandler):
for node in doxyxml.getroot():
root.append(node)
def collect_compound(self, identifier: str,
cls: List[ElementTree.Element]) -> Definition:
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')
refid = cls[0].get("refid")
if refid is None:
raise ValueError
path = os.path.join(self._doxyxml_dir, refid + ".xml")
with open(path) as f:
xml = ElementTree.parse(f)
node = xml.find('compounddef')
xml = ET.parse(f)
node = xml.find("compounddef")
if node is None:
raise ValueError
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
for m in node.findall(
'sectiondef[@kind="public-attrib"]/memberdef'
) + node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find("name")
if name is None or name.text is None:
raise ValueError
name = name.text
# Doxygen incorrectly classifies members of private unnamed unions as
# public members of the containing class.
if name.endswith('_'):
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':
kind = m.get("kind")
member = Definition(name if name else "", kind=kind, is_member=True)
type_ = m.find("type")
if type_ is None:
raise ValueError
type_text = 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
@@ -258,48 +354,60 @@ class CxxHandler(BaseHandler):
d.members.append(member)
return d
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
qual_name = 'fmt::' + identifier
@override
def collect(self, identifier: str, options: "Mapping[str, Any]") -> Definition:
qual_name = "fmt::" + identifier
param_str = None
paren = qual_name.find('(')
paren = qual_name.find("(")
if paren > 0:
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
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:]
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'
path = f"namespace{namespace.replace('::', '_1_1')}.xml"
with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = ElementTree.parse(f)
doxyxml = ET.parse(f)
self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
nodes = doxyxml.findall(f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
if len(nodes) == 0:
if self._file_doxyxml is None:
raise ValueError
nodes = self._file_doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
candidates = []
f"compounddef/sectiondef/memberdef/name[.='{name}']/.."
)
candidates: "list[str]" = []
for node in nodes:
# Process a function or a typedef.
params = None
params: "list[Definition] | None" = None
d = Definition(name, node=node)
if d.kind == 'function':
if d.kind == "function":
params = convert_params(node)
node_param_str = ', '.join([p.type for p in params])
params_type: "list[str]" = []
for p in params:
if p.type is None:
raise ValueError
else:
params_type.append(p.type)
node_param_str = ", ".join(params_type)
if param_str and param_str != node_param_str:
candidates.append(f'{name}({node_param_str})')
candidates.append(f"{name}({node_param_str})")
continue
elif d.kind == 'define':
elif d.kind == "define":
params = []
for p in node.findall('param'):
param = Definition(p.find('defname').text, kind='param')
for p in node.findall("param"):
defname = p.find("defname")
if defname is None or defname.text is None:
raise ValueError
param = Definition(defname.text, kind="param")
param.type = None
params.append(param)
d.type = convert_type(node.find('type'))
d.type = convert_type(node.find("type"))
d.template_params = convert_template_params(node)
d.params = params
convert_return_type(d, node)
@@ -308,31 +416,42 @@ class CxxHandler(BaseHandler):
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
if not cls:
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
raise Exception(f"Cannot find {identifier}. Candidates: {candidates}")
return self.collect_compound(identifier, cls)
def render(self, d: Definition, config: dict) -> str:
@override
def render(
self,
data: "CollectorItem",
options: "HandlerOptions",
*,
locale: "str | None" = None,
) -> str:
d = data
if d.id is not None:
self.do_heading('', 0, id=d.id)
_ = self.do_heading(Markup(), 0, id=d.id)
if d.desc is None:
raise ValueError
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'
text += self.render(m, options, locale=locale)
text += "</div>\n"
text += "</div>\n"
return text
def get_handler(theme: str, custom_templates: Optional[str] = None,
**_config: Any) -> CxxHandler:
def get_handler(
handler_config: "MutableMapping[str, Any]", tool_config: "MkDocsConfig", **kwargs: 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.
handler_config: The handler configuration.
tool_config: The tool (SSG) configuration.
"""
return CxxHandler(theme=theme, custom_templates=custom_templates)
base_dir = Path(tool_config.config_file_path or "./mkdocs.yml").parent
return CxxHandler(config=handler_config, base_dir=base_dir, **kwargs)

View File

@@ -1,7 +1,5 @@
add_subdirectory(gtest)
include(CheckSymbolExists)
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
add_library(test-main STATIC ${TEST_MAIN_SRC})
target_include_directories(test-main PUBLIC
@@ -62,21 +60,10 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif ()
add_fmt_test(ostream-test)
add_fmt_test(compile-test)
add_fmt_test(compile-fp-test)
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(ranges-test ranges-odr-test.cc)
add_fmt_test(no-builtin-types-test HEADER_ONLY)
add_fmt_test(scan-test HEADER_ONLY)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
if (HAVE_STRPTIME)
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
endif ()
add_fmt_test(std-test)
try_compile(compile_result_unused
${CMAKE_CURRENT_BINARY_DIR}

View File

@@ -1,62 +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 {
Char buffer[max_string_length] = {};
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
};
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

@@ -90,9 +90,6 @@ TEST(compile_test, format_escape) {
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) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@@ -124,7 +121,6 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
}
TEST(compile_test, named) {
@@ -133,10 +129,6 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
@@ -318,7 +310,6 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
}
#endif
@@ -426,6 +417,40 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) {
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
#if FMT_USE_CONSTEXPR_STRING

View File

@@ -2036,11 +2036,6 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
}
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr char static_with_null[3] = {'{', '}', '\0'};
static constexpr char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
@@ -2055,19 +2050,12 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
#endif
(void)static_with_null;
(void)static_no_null;
static constexpr char format_str[3] = {'{', '}', '\0'};
(void)format_str;
#ifndef _MSC_VER
EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42");
#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
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
#endif

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
#------------------------------------------------------------------------------
# Build the google test library
# We compile Google Test ourselves instead of using pre-compiled libraries.
# Compile Google Test ourselves instead of using pre-compiled libraries.
# See the Google Test FAQ "Why is it not recommended to install a
# pre-compiled copy of Google Test (for example, into /usr/local)?"
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
@@ -19,14 +19,10 @@ else ()
endif ()
if (MSVC)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
# Disable MSVC warnings about _CRT_INSECURE_DEPRECATE functions.
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Disable MSVC warnings of POSIX functions.
# Disable MSVC warnings about POSIX functions.
target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
endif ()
endif ()
# Silence MSVC tr1 deprecation warning in gmock.
target_compile_definitions(gtest
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)

View File

@@ -7,9 +7,10 @@
#include "gtest/gtest.h"
#if !defined(__GNUC__) || __GNUC__ >= 5
#if !defined(__GNUC__) || (__GNUC__ >= 5 || defined(__clang__))
# define FMT_BUILTIN_TYPES 0
# include "fmt/format.h"
# include "fmt/compile.h"
TEST(no_builtin_types_test, format) {
EXPECT_EQ(fmt::format("{}", 42), "42");
@@ -22,4 +23,9 @@ TEST(no_builtin_types_test, double_is_custom_type) {
EXPECT_EQ(fmt::format_args(args).get(0).type(),
fmt::detail::type::custom_type);
}
TEST(no_builtin_types_test, format_pointer_compiled) {
const void* p = nullptr;
fmt::format(FMT_COMPILE("{:} {}"), 42, p);
}
#endif

View File

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

View File

@@ -13,6 +13,7 @@
#include <vector>
#include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem
@@ -38,13 +39,12 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD <20> TAIL");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")),
"HEAD \xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")),
"HEAD <20>\xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
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
}
@@ -145,6 +145,7 @@ TEST(std_test, optional) {
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif
}
@@ -196,7 +197,33 @@ class my_class {
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
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) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
@@ -205,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{7}}),
"optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif
}
@@ -274,6 +303,24 @@ TEST(std_test, variant) {
#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) {
auto& generic = std::generic_category();
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
@@ -288,6 +335,10 @@ TEST(std_test, error_code) {
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() {