Compare commits

...

101 Commits

Author SHA1 Message Date
Victor Zverovich
95ba44311c
Add funding configuration for GitHub 2025-12-31 09:21:49 -08:00
Aaron Gokaslan
605dd2a1d9
Make digits2_i noexcept to match digits2 function (#4639)
Add noexcept specifier to digits2_i too make it match 3269c1cea5
2025-12-30 11:34:08 -08:00
user202729
7ad8004d57
Speed up do_format_decimal (#4630) 2025-12-25 08:21:20 -08:00
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: 56d7a8c157c6ff9f98dd53a092d439c736cfe934
Fixes: a3bf40838f6bf9b45544d3d3cd0f6a60e8c7218c
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
39 changed files with 1540 additions and 1161 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: vitaut

View File

@ -25,7 +25,7 @@ jobs:
language: c++ language: c++
- name: Upload crash - 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' if: failure() && steps.build.outcome == 'success'
with: with:
name: artifacts name: artifacts

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Add Ubuntu mirrors - name: Add Ubuntu mirrors
run: | run: |
@ -24,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.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 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

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@ -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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 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@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -32,7 +32,7 @@ jobs:
standard: 20 standard: 20
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone - name: Set timezone
run: tzutil /s "FLE Standard Time" run: tzutil /s "FLE Standard Time"
@ -79,7 +79,7 @@ jobs:
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - 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 }

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 # 12.0.0 - 2025-09-17
- Optimized the default floating point formatting - 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 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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. 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)
@ -150,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**
@ -178,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
@ -217,11 +217,11 @@ 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
@ -230,12 +230,12 @@ 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

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&) ::: 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
@ -624,6 +622,8 @@ Example:
::: ostream ::: ostream
::: output_file(cstring_view, T...)
::: windows_error ::: windows_error
<a id="ostream-api"></a> <a id="ostream-api"></a>
@ -706,5 +706,55 @@ following differences:
precision that provides round-trip guarantees similarly to other languages precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters `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. 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

@ -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>

View File

@ -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: 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.
@ -761,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

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

View File

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

View File

@ -429,7 +429,7 @@ template <typename Char> struct ansi_color_escape {
private: private:
static constexpr size_t num_emphases = 8; static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases]; Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0; 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,

View File

@ -15,9 +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 {};
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> {};
@ -59,6 +60,8 @@ template <detail::fixed_string Str> constexpr auto operator""_cf() {
} // namespace literals } // namespace literals
#endif #endif
FMT_END_EXPORT
namespace detail { namespace detail {
template <typename T, typename... Tail> 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> { -> 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<T>(args)...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()}; 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'; *fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
} }
auto str() const -> fmt::string_view { return {data, N - 1}; } FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
auto c_str() const -> const char* { return data; } FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
}; };
/** /**

View File

@ -30,13 +30,21 @@
# define FMT_FUNC # define FMT_FUNC
#endif #endif
#if defined(FMT_USE_FULL_CACHE_DRAGONBOX)
// Use the provided definition.
#elif defined(__OPTIMIZE_SIZE__)
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
#else
# define FMT_USE_FULL_CACHE_DRAGONBOX 1
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#ifndef FMT_CUSTOM_ASSERT_FAIL #ifndef FMT_CUSTOM_ASSERT_FAIL
FMT_FUNC void assert_fail(const char* file, int line, const char* message) { FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
// Use unchecked std::fprintf to avoid triggering another assertion when // Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails. // writing to stderr fails.
fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
abort(); abort();
} }
#endif #endif
@ -47,11 +55,6 @@ using std::locale;
using std::numpunct; using std::numpunct;
using std::use_facet; using std::use_facet;
} // namespace detail } // namespace detail
template <typename Locale, enable_if_t<(sizeof(Locale::collate) != 0), int>>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
}
#else #else
namespace detail { namespace detail {
struct locale {}; struct locale {};

View File

@ -40,11 +40,18 @@
#include "base.h" #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 #ifndef FMT_MODULE
# include <stdlib.h> // malloc, free
# include <cmath> // std::signbit # include <cmath> // std::signbit
# include <cstddef> // std::byte # include <cstddef> // std::byte
# include <cstdint> // uint32_t # include <cstdint> // uint32_t
# include <cstdlib> // std::malloc, std::free
# include <cstring> // std::memcpy # include <cstring> // std::memcpy
# include <limits> // std::numeric_limits # include <limits> // std::numeric_limits
# include <new> // std::bad_alloc # include <new> // std::bad_alloc
@ -61,11 +68,8 @@
# include <bit> // std::bit_cast # include <bit> // std::bit_cast
# endif # endif
// libc++ supports string_view in pre-c++17. # if defined(FMT_USE_STRING_VIEW)
# if FMT_HAS_INCLUDE(<string_view>) && \
(FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION))
# include <string_view> # include <string_view>
# define FMT_USE_STRING_VIEW
# endif # endif
# if FMT_MSC_VERSION # if FMT_MSC_VERSION
@ -489,8 +493,8 @@ template <typename OutputIt,
#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
__attribute__((no_sanitize("undefined"))) __attribute__((no_sanitize("undefined")))
#endif #endif
FMT_CONSTEXPR20 inline auto FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) ->
reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { typename OutputIt::value_type* {
auto& c = get_container(it); auto& c = get_container(it);
size_t size = c.size(); size_t size = c.size();
c.resize(size + n); c.resize(size + n);
@ -732,24 +736,20 @@ using fast_float_t = conditional_t<sizeof(T) == sizeof(double), double, float>;
template <typename T> template <typename T>
using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>; 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++ // 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. // ADL when applied to memory_buffer.
template <typename T> struct allocator : private std::decay<void> { template <typename T> struct allocator : private std::decay<void> {
using value_type = T; using value_type = T;
auto allocate(size_t n) -> T* { auto allocate(size_t n) -> T* {
FMT_ASSERT(n <= max_value<size_t>() / sizeof(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()); if (!p) FMT_THROW(std::bad_alloc());
return p; 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 { constexpr friend auto operator==(allocator, allocator) noexcept -> bool {
return true; // All instances of this allocator are equivalent. 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 } // namespace detail
FMT_BEGIN_EXPORT 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) * 100000, (factor) * 1000000, (factor) * 10000000, \
(factor) * 100000000, (factor) * 1000000000 (factor) * 100000000, (factor) * 1000000000
// Converts value in the range [0, 100) to a string. // Converts value in the range [0, 100) to a string. GCC generates a bit better
// GCC generates slightly better code when value is pointer-size. // code when value is pointer-size (https://www.godbolt.org/z/5fEPMT1cc).
inline auto digits2(size_t value) -> const char* { inline auto digits2(size_t value) noexcept -> const char* {
// Align data since unaligned access may be slower when crossing a // Align data since unaligned access may be slower when crossing a
// hardware-specific boundary. // hardware-specific boundary.
alignas(2) static const char data[] = alignas(2) static const char data[] =
@ -1033,6 +1041,22 @@ inline auto digits2(size_t value) -> const char* {
return &data[value * 2]; return &data[value * 2];
} }
// Given i in [0, 100), let x be the first 7 digits after
// the decimal point of i / 100 in base 2, the first 2 bytes
// after digits2_i(x) is the string representation of i.
inline auto digits2_i(size_t value) noexcept -> const char* {
alignas(2) static const char data[] =
"00010203 0405060707080910 1112"
"131414151617 18192021 222324 "
"25262728 2930313232333435 3637"
"383939404142 43444546 474849 "
"50515253 5455565757585960 6162"
"636464656667 68697071 727374 "
"75767778 7980818282838485 8687"
"888989909192 93949596 979899 ";
return &data[value * 2];
}
template <typename Char> constexpr auto getsign(sign s) -> Char { template <typename Char> constexpr auto getsign(sign s) -> Char {
return static_cast<char>(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> return static_cast<char>(((' ' << 24) | ('+' << 16) | ('-' << 8)) >>
(static_cast<int>(s) * 8)); (static_cast<int>(s) * 8));
@ -1201,6 +1225,16 @@ FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) {
*out = static_cast<Char>('0' + value % 10); *out = static_cast<Char>('0' + value % 10);
} }
template <typename Char>
FMT_INLINE void write2digits_i(Char* out, size_t value) {
if (std::is_same<Char, char>::value && !FMT_OPTIMIZE_SIZE) {
memcpy(out, digits2_i(value), 2);
return;
}
*out++ = static_cast<Char>(digits2_i(value)[0]);
*out = static_cast<Char>(digits2_i(value)[1]);
}
// Formats a decimal unsigned integer value writing to out pointing to a buffer // Formats a decimal unsigned integer value writing to out pointing to a buffer
// of specified size. The caller must ensure that the buffer is large enough. // of specified size. The caller must ensure that the buffer is large enough.
template <typename Char, typename UInt> template <typename Char, typename UInt>
@ -1209,13 +1243,20 @@ FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size)
FMT_ASSERT(size >= count_digits(value), "invalid digit count"); FMT_ASSERT(size >= count_digits(value), "invalid digit count");
unsigned n = to_unsigned(size); unsigned n = to_unsigned(size);
while (value >= 100) { while (value >= 100) {
n -= 2;
if (!is_constant_evaluated() && sizeof(UInt) == 4) {
auto p = value * static_cast<uint64_t>((1ull << 39) / 100 + 1);
write2digits_i(out + n, p >> (39 - 7) & ((1 << 7) - 1));
value = static_cast<UInt>(p >> 39) +
(static_cast<UInt>(value >= (100u << 25)) << 25);
} else {
// Integer division is slow so do it for a group of two digits instead // Integer division is slow so do it for a group of two digits instead
// of for every digit. The idea comes from the talk by Alexandrescu // of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison. // "Three Optimization Tips for C++". See speed-test for a comparison.
n -= 2;
write2digits(out + n, static_cast<unsigned>(value % 100)); write2digits(out + n, static_cast<unsigned>(value % 100));
value /= 100; value /= 100;
} }
}
if (value >= 10) { if (value >= 10) {
n -= 2; n -= 2;
write2digits(out + n, static_cast<unsigned>(value)); write2digits(out + n, static_cast<unsigned>(value));
@ -1299,7 +1340,13 @@ class utf8_to_utf16 {
inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } 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. // A converter from UTF-16/UTF-32 (host endian) to UTF-8.
template <typename WChar, typename Buffer = memory_buffer> class to_utf8 { template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
@ -1341,8 +1388,13 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
// Handle a surrogate pair. // Handle a surrogate pair.
++p; ++p;
if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
if (policy == to_utf8_error_policy::abort) return false; switch (policy) {
case to_utf8_error_policy::abort: return false;
case to_utf8_error_policy::replace:
buf.append(string_view("\xEF\xBF\xBD")); buf.append(string_view("\xEF\xBF\xBD"));
break;
case to_utf8_error_policy::wtf: to_utf8_3bytes(buf, c); break;
}
--p; --p;
continue; continue;
} }
@ -1354,9 +1406,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>(0xc0 | (c >> 6)));
buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
buf.push_back(static_cast<char>(0xe0 | (c >> 12))); to_utf8_3bytes(buf, c);
buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if (c >= 0x10000 && c <= 0x10ffff) { } else if (c >= 0x10000 && c <= 0x10ffff) {
buf.push_back(static_cast<char>(0xf0 | (c >> 18))); buf.push_back(static_cast<char>(0xf0 | (c >> 18)));
buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12))); buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
@ -2506,7 +2556,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
auto grouping = Grouping(loc, specs.localized()); auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp); size += grouping.count_separators(exp);
return write_padded<Char, align::right>( 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); if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand<Char>(it, f.significand, significand_size, it = write_significand<Char>(it, f.significand, significand_size,
f.exponent, grouping); f.exponent, grouping);
@ -2522,7 +2572,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
auto grouping = Grouping(loc, specs.localized()); auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp); size += grouping.count_separators(exp);
return write_padded<Char, align::right>( 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); if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand(it, f.significand, significand_size, exp, it = write_significand(it, f.significand, significand_size, exp,
decimal_point, grouping); decimal_point, grouping);
@ -2538,7 +2588,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
size += 1 + (pointy ? 1 : 0) + num_zeros; size += 1 + (pointy ? 1 : 0) + num_zeros;
return write_padded<Char, align::right>( 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); if (s != sign::none) *it++ = detail::getsign<Char>(s);
*it++ = Char('0'); *it++ = Char('0');
if (!pointy) return it; if (!pointy) return it;
@ -2582,7 +2632,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
*it++ = Char(exp_char); *it++ = Char(exp_char);
return write_exponent<Char>(exp, it); return write_exponent<Char>(exp, it);
}; };
auto usize = to_unsigned(size); size_t usize = static_cast<size_t>(size);
return specs.width > 0 return specs.width > 0
? write_padded<Char, align::right>(out, specs, usize, write) ? write_padded<Char, align::right>(out, specs, usize, write)
: base_iterator(out, write(reserve(out, usize))); : base_iterator(out, write(reserve(out, usize)));
@ -4130,6 +4180,14 @@ template <typename T, typename Char = char> struct nested_formatter {
inline namespace literals { inline namespace literals {
#if FMT_USE_NONTYPE_TEMPLATE_ARGS #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() { template <detail::fixed_string S> constexpr auto operator""_a() {
using char_t = remove_cvref_t<decltype(*S.data)>; using char_t = remove_cvref_t<decltype(*S.data)>;
return detail::udl_arg<char_t, sizeof(S.data) / sizeof(char_t), S>(); return detail::udl_arg<char_t, sizeof(S.data) / sizeof(char_t), S>();
@ -4236,7 +4294,11 @@ class format_int {
* // A compile-time error because 'd' is an invalid specifier for strings. * // A compile-time error because 'd' is an invalid specifier for strings.
* std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); * std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
*/ */
#if FMT_USE_CONSTEVAL
# define FMT_STRING(s) s
#else
# define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) # 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) FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args)
-> std::system_error; -> std::system_error;

View File

@ -136,10 +136,9 @@ FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
* **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) {
@ -162,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& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
}
#endif
// A buffered file. // A buffered file.
class buffered_file { class buffered_file {
private: 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 /// A fast buffered output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race. /// 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: private:
file file_; 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: public:
ostream(ostream&& other) noexcept; FMT_API ostream(ostream&& other) noexcept;
~ostream(); FMT_API ~ostream();
operator writer() { operator writer() {
detail::buffer<char>& buf = *this; detail::buffer<char>& buf = *this;

View File

@ -18,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
@ -234,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,
@ -821,12 +820,12 @@ auto join(Range&& r, string_view 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 Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> 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> { -> tuple_join_view<Tuple, char> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@ -84,11 +84,13 @@ 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>
void write_escaped_path(basic_memory_buffer<Char>& quoted, void write_escaped_path(basic_memory_buffer<Char>& quoted,
@ -111,12 +113,17 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT #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) if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v)); 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); 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 #endif
@ -139,19 +146,8 @@ template <typename Variant, typename Char> class is_variant_formattable {
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI #if FMT_USE_RTTI
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
template <typename OutputIt> char* begin) -> string_view {
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. // Normalization of stdlib inline namespace names.
// libc++ inline namespaces. // libc++ inline namespaces.
// std::__1::* -> std::* // std::__1::* -> std::*
@ -160,13 +156,12 @@ auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
// std::__cxx11::* -> std::* // std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::* // std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) { if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std:: char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size(); for (const char *from = to, *end = begin + demangled_name_view.size();
from < end;) { from < end;) {
// This is safe, because demangled_name is NUL-terminated. // This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') { if (from[0] == '_' && from[1] == '_') {
char* next = from + 1; const char* next = from + 1;
while (next < end && *next != ':') next++; while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') { if (next[0] == ':' && next[1] == ':') {
from = next + 2; from = next + 2;
@ -177,12 +172,13 @@ auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
} }
demangled_name_view = {begin, detail::to_unsigned(to - begin)}; demangled_name_view = {begin, detail::to_unsigned(to - begin)};
} }
} else { return demangled_name_view;
demangled_name_view = string_view(ti.name());
} }
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION template <class OutputIt>
const string_view demangled_name(ti.name()); 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) { for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name; auto sub = demangled_name;
sub.remove_prefix(i); 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(); if (*sub.begin() != ' ') *out++ = *sub.begin();
} }
return out; 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 # else
return detail::write_bytes<char>(out, string_view(ti.name())); return detail::write_bytes<char>(out, string_view(ti.name()));
# endif # endif
@ -255,21 +284,6 @@ template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
#if FMT_CPP_LIB_FILESYSTEM #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> { template <typename Char> struct formatter<std::filesystem::path, Char> {
private: private:
format_specs specs_; 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 #endif // FMT_CPP_LIB_FILESYSTEM
template <size_t N, typename Char> template <size_t N, typename Char>
@ -353,25 +382,16 @@ 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:
FMT_CONSTEXPR auto parse(parse_context<Char>& 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);
} }
@ -407,10 +427,10 @@ 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(");
if constexpr (!std::is_void<T>::value) 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 { } 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;
@ -474,7 +494,7 @@ struct formatter<Variant, Char,
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);
} }
@ -495,6 +515,8 @@ template <> struct formatter<std::error_code> {
bool debug_ = false; bool debug_ = false;
public: public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it == end) return it; if (it == end) return it;
@ -625,6 +647,11 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
}; };
#endif // __cpp_lib_atomic_flag_test #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> { 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_;

View File

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

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
@ -40,7 +44,7 @@ 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'
@ -64,6 +68,10 @@ if len(args) > 0:
if ret != 0 or version == 'dev': if ret != 0 or version == 'dev':
sys.exit(ret) sys.exit(ret)
current_doc_path = os.path.join(site_dir, version) 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) os.makedirs(current_doc_path, exist_ok=True)
redirect_page_path = os.path.join(current_doc_path, 'api.html') redirect_page_path = os.path.join(current_doc_path, 'api.html')
with open(redirect_page_path, "w") as file: with open(redirect_page_path, "w") as file:

View File

@ -2,190 +2,274 @@
# Copyright (c) 2012 - present, Victor Zverovich # Copyright (c) 2012 - present, Victor Zverovich
# https://github.com/fmtlib/fmt/blob/master/LICENSE # https://github.com/fmtlib/fmt/blob/master/LICENSE
# pyright: strict
import os import os
import xml.etree.ElementTree as ElementTree import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, Popen 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: class Definition:
"""A definition extracted by Doxygen.""" """A definition extracted by Doxygen."""
def __init__(self, name: str, kind: Optional[str] = None, def __init__(
node: Optional[ElementTree.Element] = None, self,
is_member: bool = False): name: str,
kind: "str | None" = None,
node: "ET.Element | None" = None,
is_member: bool = False,
):
self.name = name self.name = name
self.kind = kind if kind is not None else node.get('kind') self.kind: "str | None" = None
self.desc = None if kind is not None:
self.id = name if not is_member else None self.kind = kind
self.members = None elif node is not None:
self.params = None self.kind = node.get("kind")
self.template_params = None self.desc: "list[ET.Element[str]] | None" = None
self.trailing_return_type = None self.id: "str | None" = name if not is_member else None
self.type = 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. # 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": "", "highlight": "", "sp": " "}
'codeline': '',
'highlight': '',
'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[ElementTree.Element]): # Converts a node from doxygen to HTML format.
out = '' def convert_node(
for n in nodes: node: ET.Element, tag: str, attrs: "Mapping[str, str] | None" = None
tag = tag_map.get(n.tag) ) -> str:
if not tag: if attrs is None:
out += tag_text_map[n.tag] attrs = {}
out += '<' + tag + '>' if tag else ''
out += '<code class="language-cpp">' if tag == 'pre' else '' out: str = "<" + tag
if n.text: for key, value in attrs.items():
out += escape_html(n.text) out += " " + key + '="' + value + '"'
out += doxyxml2html(list(n)) out += ">"
out += '</code>' if tag == 'pre' else '' if node.text:
out += '</' + tag + '>' if tag else '' out += escape_html(node.text)
if n.tail: out += doxyxml2html(list(node))
out += n.tail out += "</" + tag + ">"
if node.tail:
out += node.tail
return out return out
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]: def doxyxml2html(nodes: "list[ET.Element]"):
template_param_list = node.find('templateparamlist') 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: if template_param_list is None:
return None return None
params = [] params: "list[Definition]" = []
for param_node in template_param_list.findall('param'): for param_node in template_param_list.findall("param"):
name = param_node.find('declname') name = param_node.find("declname")
param = Definition(name.text if name is not None else '', 'param') if name is not None:
param.type = param_node.find('type').text 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) params.append(param)
return params return params
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: def get_description(node: ET.Element) -> list[ET.Element]:
return node.findall('briefdescription/para') + \ return node.findall("briefdescription/para") + node.findall(
node.findall('detaileddescription/para') "detaileddescription/para"
)
def normalize_type(type_: str) -> str: def normalize_type(type_: str) -> str:
type_ = type_.replace('< ', '<').replace(' >', '>') type_ = type_.replace("< ", "<").replace(" >", ">")
return 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: if type_ is None:
return None return None
result = type_.text if type_.text else '' result = type_.text if type_.text else ""
for ref in type_: for ref in type_:
if ref.text is None:
raise ValueError
result += ref.text result += ref.text
if ref.tail: if ref.tail:
result += ref.tail result += ref.tail
if type_.tail is None:
raise ValueError
result += type_.tail.strip() result += type_.tail.strip()
return normalize_type(result) return normalize_type(result)
def convert_params(func: ElementTree.Element) -> List[Definition]: def convert_params(func: ET.Element) -> list[Definition]:
params = [] params: "list[Definition]" = []
for p in func.findall('param'): for p in func.findall("param"):
d = Definition(p.find('declname').text, 'param') declname = p.find("declname")
d.type = convert_type(p.find('type')) 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) params.append(d)
return params 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 d.trailing_return_type = None
if d.type == 'auto' or d.type == 'constexpr auto': if d.type == "auto" or d.type == "constexpr auto":
parts = node.find('argsstring').text.split(' -> ') argsstring = node.find("argsstring")
if argsstring is None or argsstring.text is None:
raise ValueError
parts = argsstring.text.split(" -> ")
if len(parts) > 1: if len(parts) > 1:
d.trailing_return_type = normalize_type(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 '') 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: def render_decl(d: Definition) -> str:
text = '' text = ""
if d.id is not None: if d.id is not None:
text += f'<a id="{d.id}">\n' text += f'<a id="{d.id}">\n'
text += '<pre><code class="language-cpp decl">' text += '<pre><code class="language-cpp decl">'
text += '<div>' text += "<div>"
if d.template_params is not None: if d.template_params is not None:
text += 'template &lt;' text += "template &lt;"
text += ', '.join([render_param(p) for p in d.template_params]) text += ", ".join([render_param(p) for p in d.template_params])
text += '&gt;\n' text += "&gt;\n"
text += '</div>' text += "</div>"
text += '<div>' text += "<div>"
end = ';' end = ";"
if d.kind == 'function' or d.kind == 'variable': if d.kind is None:
text += d.type + ' ' if len(d.type) > 0 else '' raise ValueError
elif d.kind == 'typedef': if d.kind == "function" or d.kind == "variable":
text += 'using ' if d.type is None:
elif d.kind == 'define': raise ValueError
end = '' text += d.type + " " if len(d.type) > 0 else ""
elif d.kind == "typedef":
text += "using "
elif d.kind == "define":
end = ""
else: else:
text += d.kind + ' ' text += d.kind + " "
text += d.name text += d.name
if d.params is not None: if d.params is not None:
params = ', '.join([ params = ", ".join([
(p.type + ' ' if p.type else '') + p.name for p in d.params]) (p.type + " " if p.type else "") + p.name for p in d.params
text += '(' + escape_html(params) + ')' ])
text += "(" + escape_html(params) + ")"
if d.trailing_return_type: if d.trailing_return_type:
text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type) text += " -&NoBreak;>&nbsp;" + escape_html(d.trailing_return_type)
elif d.kind == 'typedef': elif d.kind == "typedef":
text += ' = ' + escape_html(d.type) if d.type is None:
raise ValueError
text += " = " + escape_html(d.type)
text += end text += end
text += '</div>' text += "</div>"
text += '</code></pre>\n' text += "</code></pre>\n"
if d.id is not None: if d.id is not None:
text += f'</a>\n' text += "</a>\n"
return text return text
@final
class CxxHandler(BaseHandler): class CxxHandler(BaseHandler):
def __init__(self, **kwargs: Any) -> None: name: ClassVar[str] = "cxx"
super().__init__(handler='cxx', **kwargs)
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 = [ headers = [
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h', "args.h",
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.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. # Run doxygen.
cmd = ['doxygen', '-'] cmd = ["doxygen", "-"]
support_dir = Path(__file__).parents[3] support_dir = Path(__file__).parents[3]
top_dir = os.path.dirname(support_dir) top_dir = os.path.dirname(support_dir)
include_dir = os.path.join(top_dir, 'include', 'fmt') include_dir = os.path.join(top_dir, "include", "fmt")
self._ns2doxyxml = {} self._ns2doxyxml: "dict[str, ET.ElementTree[ET.Element[str]]]" = {}
build_dir = os.path.join(top_dir, 'build') build_dir = os.path.join(top_dir, "build")
os.makedirs(build_dir, exist_ok=True) 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 = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
_, _ = p.communicate(input=r''' _, _ = p.communicate(
input=r"""
PROJECT_NAME = fmt PROJECT_NAME = fmt
GENERATE_XML = YES GENERATE_XML = YES
GENERATE_LATEX = NO GENERATE_LATEX = NO
@ -205,18 +289,20 @@ class CxxHandler(BaseHandler):
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \ "FMT_END_NAMESPACE=}}" \
"FMT_DOC=1" "FMT_DOC=1"
'''.format( """.format(
' '.join([os.path.join(include_dir, h) for h in headers]), " ".join([os.path.join(include_dir, h) for h in headers]),
self._doxyxml_dir).encode('utf-8')) self._doxyxml_dir,
).encode("utf-8")
)
if p.returncode != 0: if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd) raise CalledProcessError(p.returncode, cmd)
# Merge all file-level XMLs into one to simplify search. # 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: for h in headers:
filename = h.replace(".h", "_8h.xml") filename = h.replace(".h", "_8h.xml")
with open(os.path.join(self._doxyxml_dir, filename)) as f: 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: if self._file_doxyxml is None:
self._file_doxyxml = doxyxml self._file_doxyxml = doxyxml
continue continue
@ -224,33 +310,43 @@ class CxxHandler(BaseHandler):
for node in doxyxml.getroot(): for node in doxyxml.getroot():
root.append(node) root.append(node)
def collect_compound(self, identifier: str, def collect_compound(self, identifier: str, cls: "list[ET.Element]") -> Definition:
cls: List[ElementTree.Element]) -> Definition:
"""Collect a compound definition such as a struct.""" """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: with open(path) as f:
xml = ElementTree.parse(f) xml = ET.parse(f)
node = xml.find('compounddef') node = xml.find("compounddef")
if node is None:
raise ValueError
d = Definition(identifier, node=node) d = Definition(identifier, node=node)
d.template_params = convert_template_params(node) d.template_params = convert_template_params(node)
d.desc = get_description(node) d.desc = get_description(node)
d.members = [] d.members = []
for m in \ for m in node.findall(
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ 'sectiondef[@kind="public-attrib"]/memberdef'
node.findall('sectiondef[@kind="public-func"]/memberdef'): ) + node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find('name').text 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 # Doxygen incorrectly classifies members of private unnamed unions as
# public members of the containing class. # public members of the containing class.
if name.endswith('_'): if name.endswith("_"):
continue continue
desc = get_description(m) desc = get_description(m)
if len(desc) == 0: if len(desc) == 0:
continue continue
kind = m.get('kind') kind = m.get("kind")
member = Definition(name if name else '', kind=kind, is_member=True) member = Definition(name if name else "", kind=kind, is_member=True)
type_text = m.find('type').text type_ = m.find("type")
member.type = type_text if type_text else '' if type_ is None:
if kind == 'function': raise ValueError
type_text = type_.text
member.type = type_text if type_text else ""
if kind == "function":
member.params = convert_params(m) member.params = convert_params(m)
convert_return_type(member, m) convert_return_type(member, m)
member.template_params = None member.template_params = None
@ -258,48 +354,60 @@ class CxxHandler(BaseHandler):
d.members.append(member) d.members.append(member)
return d return d
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition: @override
qual_name = 'fmt::' + identifier def collect(self, identifier: str, options: "Mapping[str, Any]") -> Definition:
qual_name = "fmt::" + identifier
param_str = None param_str = None
paren = qual_name.find('(') paren = qual_name.find("(")
if paren > 0: 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('::') colons = qual_name.rfind("::")
namespace, name = qual_name[:colons], qual_name[colons + 2 :] namespace, name = qual_name[:colons], qual_name[colons + 2 :]
# Load XML. # Load XML.
doxyxml = self._ns2doxyxml.get(namespace) doxyxml = self._ns2doxyxml.get(namespace)
if doxyxml is None: 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: with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = ElementTree.parse(f) doxyxml = ET.parse(f)
self._ns2doxyxml[namespace] = doxyxml self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall( nodes = doxyxml.findall(f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
if len(nodes) == 0: if len(nodes) == 0:
if self._file_doxyxml is None:
raise ValueError
nodes = self._file_doxyxml.findall( nodes = self._file_doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..") f"compounddef/sectiondef/memberdef/name[.='{name}']/.."
candidates = [] )
candidates: "list[str]" = []
for node in nodes: for node in nodes:
# Process a function or a typedef. # Process a function or a typedef.
params = None params: "list[Definition] | None" = None
d = Definition(name, node=node) d = Definition(name, node=node)
if d.kind == 'function': if d.kind == "function":
params = convert_params(node) 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: if param_str and param_str != node_param_str:
candidates.append(f'{name}({node_param_str})') candidates.append(f"{name}({node_param_str})")
continue continue
elif d.kind == 'define': elif d.kind == "define":
params = [] params = []
for p in node.findall('param'): for p in node.findall("param"):
param = Definition(p.find('defname').text, kind='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 param.type = None
params.append(param) 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.template_params = convert_template_params(node)
d.params = params d.params = params
convert_return_type(d, node) convert_return_type(d, node)
@ -308,31 +416,42 @@ class CxxHandler(BaseHandler):
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']") cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
if not cls: 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) 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: 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 = '<div class="docblock">\n'
text += render_decl(d) text += render_decl(d)
text += '<div class="docblock-desc">\n' text += '<div class="docblock-desc">\n'
text += doxyxml2html(d.desc) text += doxyxml2html(d.desc)
if d.members is not None: if d.members is not None:
for m in d.members: for m in d.members:
text += self.render(m, config) text += self.render(m, options, locale=locale)
text += '</div>\n' text += "</div>\n"
text += '</div>\n' text += "</div>\n"
return text return text
def get_handler(theme: str, custom_templates: Optional[str] = None, def get_handler(
**_config: Any) -> CxxHandler: handler_config: "MutableMapping[str, Any]", tool_config: "MkDocsConfig", **kwargs: Any
) -> CxxHandler:
"""Return an instance of `CxxHandler`. """Return an instance of `CxxHandler`.
Arguments: Arguments:
theme: The theme to use when rendering contents. handler_config: The handler configuration.
custom_templates: Directory containing custom templates. tool_config: The tool (SSG) configuration.
**_config: Configuration passed to the handler.
""" """
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) add_subdirectory(gtest)
include(CheckSymbolExists)
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
add_library(test-main STATIC ${TEST_MAIN_SRC}) add_library(test-main STATIC ${TEST_MAIN_SRC})
target_include_directories(test-main PUBLIC target_include_directories(test-main PUBLIC
@ -62,21 +60,10 @@ 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)
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(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)
if (HAVE_STRPTIME)
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
endif ()
add_fmt_test(std-test) add_fmt_test(std-test)
try_compile(compile_result_unused try_compile(compile_result_unused
${CMAKE_CURRENT_BINARY_DIR} ${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")); 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));
@ -124,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) {
@ -133,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")));
@ -318,7 +310,6 @@ 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 #endif
@ -426,6 +417,40 @@ 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 #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')); 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) { 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");
@ -2055,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

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

@ -1,7 +1,7 @@
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Build the google test library # 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 # See the Google Test FAQ "Why is it not recommended to install a
# pre-compiled copy of Google Test (for example, into /usr/local)?" # pre-compiled copy of Google Test (for example, into /usr/local)?"
# at http://code.google.com/p/googletest/wiki/FAQ for more details. # at http://code.google.com/p/googletest/wiki/FAQ for more details.
@ -19,14 +19,10 @@ else ()
endif () endif ()
if (MSVC) 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) target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 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) target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
endif () endif ()
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" #include "gtest/gtest.h"
#if !defined(__GNUC__) || __GNUC__ >= 5 #if !defined(__GNUC__) || (__GNUC__ >= 5 || defined(__clang__))
# define FMT_BUILTIN_TYPES 0 # define FMT_BUILTIN_TYPES 0
# include "fmt/format.h" # include "fmt/compile.h"
TEST(no_builtin_types_test, format) { TEST(no_builtin_types_test, format) {
EXPECT_EQ(fmt::format("{}", 42), "42"); 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(), EXPECT_EQ(fmt::format_args(args).get(0).type(),
fmt::detail::type::custom_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 #endif

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) {

View File

@ -13,6 +13,7 @@
#include <vector> #include <vector>
#include "fmt/os.h" // fmt::system_category #include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith #include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem #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" 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"HEAD \xd800 TAIL")), "HEAD <20> TAIL"); EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")), EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
"HEAD \xF0\x9F\x98\x80 TAIL"); EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")), "[\xED\xA0\xBD\xF0\x9F\x98\x80]");
"HEAD <20>\xF0\x9F\x98\x80 TAIL"); EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
# endif # endif
} }
@ -145,6 +145,7 @@ 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
} }
@ -196,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");
@ -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>{}), "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
} }
@ -274,6 +303,24 @@ 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) {
auto& generic = std::generic_category(); auto& generic = std::generic_category();
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42"); 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("{:s}", ec), ec.message());
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)), EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
"\"generic:42\""); "\"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() {