mirror of
https://github.com/fmtlib/fmt.git
synced 2025-12-23 15:28:27 +01:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91d1aced0a | ||
|
|
2e819a11f2 | ||
|
|
7bce22571a | ||
|
|
ec73fb7247 | ||
|
|
3269c1cea5 | ||
|
|
789aa69e0a | ||
|
|
2a50a0d808 | ||
|
|
02d6f3d9e4 | ||
|
|
19e4d5ecd9 | ||
|
|
a6e871e39b | ||
|
|
e137db699a | ||
|
|
d6712ff2c0 | ||
|
|
2d839bbc61 | ||
|
|
5bc56e24a9 | ||
|
|
2727215c11 | ||
|
|
790b9389ae | ||
|
|
a731c73fd5 | ||
|
|
3391f9e992 | ||
|
|
9f197b22ae | ||
|
|
f20b16617e | ||
|
|
14451704d5 | ||
|
|
7a0da1c68a | ||
|
|
e3d2174b3c | ||
|
|
a6fb4d3b06 | ||
|
|
706fecea30 | ||
|
|
e00fd756cc | ||
|
|
fc17e825d9 | ||
|
|
3fccfb8a80 | ||
|
|
690c9c71a0 | ||
|
|
1122268510 | ||
|
|
a4c7e17133 | ||
|
|
bfd0129b91 | ||
|
|
62f57b2496 | ||
|
|
ef7a566413 | ||
|
|
42840cb415 | ||
|
|
5ac44cd128 | ||
|
|
23c13b3060 | ||
|
|
29c46fb82d | ||
|
|
a0571f3f59 | ||
|
|
a195dd6b37 | ||
|
|
33ad559eb8 | ||
|
|
b6cd356196 | ||
|
|
c3be070b7e | ||
|
|
27bf8b47fe | ||
|
|
407c905e45 | ||
|
|
f781d2b932 | ||
|
|
5987082c47 | ||
|
|
681c9e689b | ||
|
|
913507044b | ||
|
|
ff357e9e4a | ||
|
|
728dfeab5b | ||
|
|
7adc922ebb | ||
|
|
70ed0ab82a | ||
|
|
b95fd6132b | ||
|
|
7241bbb149 | ||
|
|
c0ddbacfd3 | ||
|
|
9395ef5fcb | ||
|
|
9721d974fc | ||
|
|
d6bdb69c62 | ||
|
|
08d38d6e78 | ||
|
|
a2289b8593 | ||
|
|
e8da5ba275 | ||
|
|
e2aa06cd0a | ||
|
|
85f6ecc7a0 | ||
|
|
378a5ab3c1 | ||
|
|
656d14db8b | ||
|
|
31bed1bb65 | ||
|
|
8eebb4334b | ||
|
|
beefc1c14f | ||
|
|
81fe170849 | ||
|
|
491dc16a6d | ||
|
|
41326207c7 | ||
|
|
c4f70ab69e | ||
|
|
5e214f0c43 | ||
|
|
1234bc312e | ||
|
|
b77a751625 | ||
|
|
8db24c0ea9 | ||
|
|
03c7e28965 | ||
|
|
47a18b2fe9 | ||
|
|
eb44d87b52 | ||
|
|
b580360ab7 | ||
|
|
486e7ba579 | ||
|
|
27ea09836a | ||
|
|
b5b9317a3c | ||
|
|
d13e5d048d | ||
|
|
b9ac8b225d | ||
|
|
4801f54e59 | ||
|
|
5f66e07cb0 | ||
|
|
17be91c079 | ||
|
|
dcea616535 | ||
|
|
a2fd48e039 | ||
|
|
e28c371c0f | ||
|
|
6b6cdd9405 | ||
|
|
c5e55972ae | ||
|
|
dc409ee86d | ||
|
|
4cce5f458d | ||
|
|
aa8a30838a | ||
|
|
b18ece7d71 |
2
.github/workflows/cifuzz.yml
vendored
2
.github/workflows/cifuzz.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
language: c++
|
||||
|
||||
- name: Upload crash
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
4
.github/workflows/doc.yml
vendored
4
.github/workflows/doc.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Add Ubuntu mirrors
|
||||
run: |
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install doxygen
|
||||
pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
|
||||
pip install mkdocs-material==9.7.0 mkdocstrings==1.0.0 mike==2.1.3 typing_extensions==4.15.0
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
# Workaround https://github.com/actions/checkout/issues/13:
|
||||
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -13,16 +13,16 @@ jobs:
|
||||
format_code:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
sudo bash ./llvm.sh 17
|
||||
sudo apt install clang-format-17
|
||||
sudo bash ./llvm.sh 21
|
||||
sudo apt install clang-format-21
|
||||
|
||||
- name: Run clang-format
|
||||
run: |
|
||||
find include src -name '*.h' -o -name '*.cc' | \
|
||||
xargs clang-format-17 -i -style=file -fallback-style=none
|
||||
xargs clang-format-21 -i -style=file -fallback-style=none
|
||||
git diff --exit-code
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
install: sudo apt install libc++-14-dev libc++abi-14-dev
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set timezone
|
||||
run: sudo timedatectl set-timezone 'Europe/Kyiv'
|
||||
|
||||
9
.github/workflows/macos.yml
vendored
9
.github/workflows/macos.yml
vendored
@@ -9,13 +9,10 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-13, macos-14]
|
||||
os: [macos-14]
|
||||
build_type: [Debug, Release]
|
||||
std: [11, 17, 20]
|
||||
std: [11, 17, 20, 23]
|
||||
shared: [""]
|
||||
exclude:
|
||||
- { os: macos-13, std: 11 }
|
||||
- { os: macos-13, std: 17 }
|
||||
include:
|
||||
- os: macos-14
|
||||
std: 23
|
||||
@@ -25,7 +22,7 @@ jobs:
|
||||
runs-on: '${{ matrix.os }}'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set timezone
|
||||
run: sudo systemsetup -settimezone 'Europe/Minsk'
|
||||
|
||||
6
.github/workflows/scorecard.yml
vendored
6
.github/workflows/scorecard.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -60,6 +60,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
|
||||
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
standard: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set timezone
|
||||
run: tzutil /s "FLE Standard Time"
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
release: false
|
||||
msystem: ${{matrix.sys}}
|
||||
pacboy: cc:p cmake:p ninja:p lld:p
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Configure
|
||||
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
env: { LDFLAGS: -fuse-ld=lld }
|
||||
|
||||
74
ChangeLog.md
74
ChangeLog.md
@@ -1,3 +1,77 @@
|
||||
# 12.1.0 - 2025-10-29
|
||||
|
||||
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
|
||||
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
|
||||
|
||||
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
|
||||
gcc (https://github.com/fmtlib/fmt/issues/4573).
|
||||
|
||||
- Made `std::variant` and `std::expected` formatters work with `format_as`
|
||||
(https://github.com/fmtlib/fmt/issues/4574,
|
||||
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
|
||||
|
||||
- Made `fmt::join<string_view>` work with C++ modules
|
||||
(https://github.com/fmtlib/fmt/issues/4379,
|
||||
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
|
||||
|
||||
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
|
||||
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
|
||||
|
||||
- Fixed a compatibility issue with C++ modules in clang
|
||||
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
|
||||
|
||||
- Added support for cv-qualified types to the `std::optional` formatter
|
||||
(https://github.com/fmtlib/fmt/issues/4561,
|
||||
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
|
||||
|
||||
- Added demangling support (used in exception and `std::type_info` formatters)
|
||||
for libc++ and clang-cl
|
||||
(https://github.com/fmtlib/fmt/issues/4542,
|
||||
https://github.com/fmtlib/fmt/pull/4560,
|
||||
https://github.com/fmtlib/fmt/issues/4568,
|
||||
https://github.com/fmtlib/fmt/pull/4571).
|
||||
Thanks @FatihBAKIR and @rohitsutreja.
|
||||
|
||||
- Switched to global `malloc`/`free` to enable allocator customization
|
||||
(https://github.com/fmtlib/fmt/issues/4569,
|
||||
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
|
||||
|
||||
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
|
||||
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
|
||||
|
||||
- Fixed compilation with locales disabled in the header-only mode
|
||||
(https://github.com/fmtlib/fmt/issues/4550).
|
||||
|
||||
- Fixed compilation with clang 21 and `-std=c++20`
|
||||
(https://github.com/fmtlib/fmt/issues/4552).
|
||||
|
||||
- Fixed a dynamic linking issue with clang-cl
|
||||
(https://github.com/fmtlib/fmt/issues/4576,
|
||||
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
|
||||
|
||||
- Fixed a warning suppression leakage on gcc
|
||||
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
|
||||
|
||||
- Made more internal color APIs `constexpr`
|
||||
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
|
||||
|
||||
- Fixed compatibility with clang as a host compiler for NVCC
|
||||
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
|
||||
|
||||
- Fixed various warnings and lint issues
|
||||
(https://github.com/fmtlib/fmt/issues/4565,
|
||||
https://github.com/fmtlib/fmt/pull/4572,
|
||||
https://github.com/fmtlib/fmt/pull/4557).
|
||||
Thanks @LiangHuDream and @teruyamato0731.
|
||||
|
||||
- Improved documentation
|
||||
(https://github.com/fmtlib/fmt/issues/4549,
|
||||
https://github.com/fmtlib/fmt/pull/4551,
|
||||
https://github.com/fmtlib/fmt/issues/4566,
|
||||
https://github.com/fmtlib/fmt/pull/4567,
|
||||
https://github.com/fmtlib/fmt/pull/4578,).
|
||||
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
|
||||
|
||||
# 12.0.0 - 2025-09-17
|
||||
|
||||
- Optimized the default floating point formatting
|
||||
|
||||
7
LICENSE
7
LICENSE
@@ -18,10 +18,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
||||
|
||||
48
README.md
48
README.md
@@ -12,7 +12,7 @@
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
If you like this project, please consider donating to one of the funds
|
||||
that help victims of the war in Ukraine: <https://www.stopputin.net/>.
|
||||
that help victims of the war in Ukraine: <https://u24.gov.ua/>.
|
||||
|
||||
[Documentation](https://fmt.dev)
|
||||
|
||||
@@ -150,8 +150,8 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
This can be [5 to 9 times faster than
|
||||
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
|
||||
This can be [up to 9 times faster than `fprintf`](
|
||||
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
|
||||
|
||||
**Print with colors and text styles**
|
||||
|
||||
@@ -178,17 +178,17 @@ Output on a modern terminal with Unicode support:
|
||||
|
||||
| Library | Method | Run Time, s |
|
||||
|-------------------|---------------|-------------|
|
||||
| libc | printf | 0.91 |
|
||||
| libc++ | std::ostream | 2.49 |
|
||||
| {fmt} 9.1 | fmt::print | 0.74 |
|
||||
| Boost Format 1.80 | boost::format | 6.26 |
|
||||
| Folly Format | folly::format | 1.87 |
|
||||
| libc | printf | 0.66 |
|
||||
| libc++ | std::ostream | 1.63 |
|
||||
| {fmt} 12.1 | fmt::print | 0.44 |
|
||||
| Boost Format 1.88 | boost::format | 3.89 |
|
||||
| Folly Format | folly::format | 1.28 |
|
||||
|
||||
{fmt} is the fastest of the benchmarked methods, \~20% faster than
|
||||
{fmt} is the fastest of the benchmarked methods, \~50% faster than
|
||||
`printf`.
|
||||
|
||||
The above results were generated by building `tinyformat_test.cpp` on
|
||||
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
|
||||
macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
|
||||
taking the best of three runs. In the test, the format string
|
||||
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
|
||||
times with output sent to `/dev/null`; for further details refer to the
|
||||
@@ -216,26 +216,26 @@ in the following tables.
|
||||
|
||||
**Optimized build (-O3)**
|
||||
|
||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||
|---------------|-----------------|----------------------|--------------------|
|
||||
| printf | 1.6 | 54 | 50 |
|
||||
| IOStreams | 25.9 | 98 | 84 |
|
||||
| fmt 83652df | 4.8 | 54 | 50 |
|
||||
| tinyformat | 29.1 | 161 | 136 |
|
||||
| Boost Format | 55.0 | 530 | 317 |
|
||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||
|-----------------|-----------------|----------------------|--------------------|
|
||||
| printf | 1.6 | 54 | 50 |
|
||||
| IOStreams | 28.4 | 98 | 84 |
|
||||
| {fmt} `1122268` | 5.0 | 54 | 50 |
|
||||
| tinyformat | 32.6 | 164 | 136 |
|
||||
| Boost Format | 55.0 | 530 | 317 |
|
||||
|
||||
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
|
||||
binary size (within a rounding error on this system).
|
||||
|
||||
**Non-optimized build**
|
||||
|
||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||
|---------------|-----------------|----------------------|--------------------|
|
||||
| printf | 1.4 | 54 | 50 |
|
||||
| IOStreams | 23.4 | 92 | 68 |
|
||||
| {fmt} 83652df | 4.4 | 89 | 85 |
|
||||
| tinyformat | 24.5 | 204 | 161 |
|
||||
| Boost Format | 36.4 | 831 | 462 |
|
||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||
|-----------------|-----------------|----------------------|--------------------|
|
||||
| printf | 1.4 | 54 | 50 |
|
||||
| IOStreams | 27.0 | 88 | 68 |
|
||||
| {fmt} `1122268` | 4.7 | 87 | 84 |
|
||||
| tinyformat | 28.1 | 185 | 145 |
|
||||
| Boost Format | 38.9 | 678 | 381 |
|
||||
|
||||
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
|
||||
to compare formatting function overhead only. Boost Format is a
|
||||
|
||||
27
doc/LICENSE-exception
Normal file
27
doc/LICENSE-exception
Normal 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.
|
||||
56
doc/api.md
56
doc/api.md
@@ -321,8 +321,6 @@ parameterized version.
|
||||
|
||||
::: arg(const Char*, const T&)
|
||||
|
||||
Named arguments are not supported in compile-time checks at the moment.
|
||||
|
||||
### Compatibility
|
||||
|
||||
::: basic_string_view
|
||||
@@ -624,6 +622,8 @@ Example:
|
||||
|
||||
::: ostream
|
||||
|
||||
::: output_file(cstring_view, T...)
|
||||
|
||||
::: windows_error
|
||||
|
||||
<a id="ostream-api"></a>
|
||||
@@ -706,5 +706,55 @@ following differences:
|
||||
precision that provides round-trip guarantees similarly to other languages
|
||||
like Java and Python. `std::format` is currently specified in terms of
|
||||
`std::to_chars` which tries to generate the smallest number of characters
|
||||
(ignoring redundant digits and sign in exponent) and may procude more
|
||||
(ignoring redundant digits and sign in exponent) and may produce more
|
||||
decimal digits than necessary.
|
||||
|
||||
## Configuration Options
|
||||
|
||||
{fmt} provides configuration via CMake options and preprocessor macros to
|
||||
enable or disable features and to optimize for binary size. For example, you
|
||||
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
|
||||
configuring CMake.
|
||||
|
||||
### CMake Options
|
||||
|
||||
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
|
||||
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
|
||||
Windows/MSVC. Unicode support is always enabled on other platforms.
|
||||
|
||||
### Macros
|
||||
|
||||
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
|
||||
alternative to using the `fmt::fmt-header-only` CMake target.
|
||||
Default: not defined.
|
||||
|
||||
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
|
||||
Default: `1` (`0` if compiled with `-fno-exceptions`).
|
||||
|
||||
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
|
||||
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
|
||||
|
||||
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
|
||||
custom `fmt::assert_fail` function which is called on assertion failures and,
|
||||
if exceptions are disabled, on runtime errors. Default: `0`.
|
||||
|
||||
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
|
||||
arithmetic and string types other than `int`. This reduces library size at
|
||||
the cost of per-call overhead. Default: `1`.
|
||||
|
||||
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
|
||||
- `0` - off (default)
|
||||
- `1` - disables locale support and applies some optimizations
|
||||
- `2` - disables some Unicode features, named arguments and applies more
|
||||
aggressive optimizations
|
||||
|
||||
### Binary Size Optimization
|
||||
|
||||
To minimize the binary footprint of {fmt} as much as possible at the cost of
|
||||
some features, you can use the following configuration:
|
||||
|
||||
- CMake options:
|
||||
- `FMT_OS=OFF`
|
||||
- Macros:
|
||||
- `FMT_BUILTIN_TYPES=0`
|
||||
- `FMT_OPTIMIZE_SIZE=2`
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
code,
|
||||
pre > code.decl {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ hide:
|
||||
<p>
|
||||
The default is <b>locale-independent</b>, but you can opt into localized
|
||||
formatting and {fmt} makes it work with Unicode, addressing issues in the
|
||||
standard libary.
|
||||
standard library.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ The available integer presentation types are:
|
||||
<td><code>'b'</code></td>
|
||||
<td>
|
||||
Binary format. Outputs the number in base 2. Using the <code>'#'</code>
|
||||
option with this type adds the prefix <code>"0b"</code> to the output value.
|
||||
option with this type adds the prefix <code>"0b"</code> to the output value.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -718,7 +718,7 @@ These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
|
||||
Format specifications for range types have the following syntax:
|
||||
|
||||
<pre><code class="language-json"
|
||||
>range_format_spec ::= ["n"][range_type][range_underlying_spec]</code>
|
||||
>range_format_spec ::= ["n"][range_type][":" range_underlying_spec]</code>
|
||||
</pre>
|
||||
|
||||
The `'n'` option formats the range without the opening and closing brackets.
|
||||
@@ -761,14 +761,16 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
|
||||
// Output: [h, e, l, l, o]
|
||||
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
|
||||
// Output: [104, 101, 108, 108, 111]
|
||||
fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e});
|
||||
// Output: 3.141593, 2.718282
|
||||
```
|
||||
|
||||
## Format Examples
|
||||
|
||||
This section contains examples of the format syntax and comparison with
|
||||
the printf formatting.
|
||||
the `printf` formatting.
|
||||
|
||||
In most of the cases the syntax is similar to the printf formatting,
|
||||
In most of the cases the syntax is similar to the `printf` formatting,
|
||||
with the addition of the `{}` and with `:` used instead of `%`. For
|
||||
example, `"%03.2f"` can be translated to `"{:03.2f}"`.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#endif
|
||||
|
||||
// The fmt library version in the form major * 10000 + minor * 100 + patch.
|
||||
#define FMT_VERSION 120000
|
||||
#define FMT_VERSION 120101
|
||||
|
||||
// Detect compiler versions.
|
||||
#if defined(__clang__) && !defined(__ibmxl__)
|
||||
@@ -114,7 +114,9 @@
|
||||
#endif
|
||||
|
||||
// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated.
|
||||
#if !defined(__cpp_lib_is_constant_evaluated)
|
||||
#ifdef FMT_USE_CONSTEVAL
|
||||
// Use the provided definition.
|
||||
#elif !defined(__cpp_lib_is_constant_evaluated)
|
||||
# define FMT_USE_CONSTEVAL 0
|
||||
#elif FMT_CPLUSPLUS < 201709L
|
||||
# define FMT_USE_CONSTEVAL 0
|
||||
@@ -231,9 +233,9 @@
|
||||
FMT_PRAGMA_GCC(push_options)
|
||||
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
|
||||
FMT_PRAGMA_GCC(optimize("Og"))
|
||||
# define FMT_GCC_OPTIMIZED
|
||||
#endif
|
||||
FMT_PRAGMA_CLANG(diagnostic push)
|
||||
FMT_PRAGMA_GCC(diagnostic push)
|
||||
|
||||
#ifdef FMT_ALWAYS_INLINE
|
||||
// Use the provided definition.
|
||||
@@ -243,7 +245,7 @@ FMT_PRAGMA_CLANG(diagnostic push)
|
||||
# define FMT_ALWAYS_INLINE inline
|
||||
#endif
|
||||
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
|
||||
#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
|
||||
#ifdef NDEBUG
|
||||
# define FMT_INLINE FMT_ALWAYS_INLINE
|
||||
#else
|
||||
# define FMT_INLINE inline
|
||||
@@ -414,8 +416,12 @@ inline auto map(int128_opt) -> monostate { return {}; }
|
||||
inline auto map(uint128_opt) -> monostate { return {}; }
|
||||
#endif
|
||||
|
||||
#ifndef FMT_USE_BITINT
|
||||
# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500)
|
||||
#ifdef FMT_USE_BITINT
|
||||
// Use the provided definition.
|
||||
#elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__)
|
||||
# define FMT_USE_BITINT 1
|
||||
#else
|
||||
# define FMT_USE_BITINT 0
|
||||
#endif
|
||||
|
||||
#if FMT_USE_BITINT
|
||||
@@ -918,9 +924,15 @@ class locale_ref {
|
||||
constexpr locale_ref() : locale_(nullptr) {}
|
||||
|
||||
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
|
||||
locale_ref(const Locale& loc);
|
||||
locale_ref(const Locale& loc) : locale_(&loc) {
|
||||
// Check if std::isalpha is found via ADL to reduce the chance of misuse.
|
||||
detail::ignore_unused(sizeof(isalpha('x', loc)));
|
||||
}
|
||||
|
||||
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
|
||||
#else
|
||||
public:
|
||||
inline explicit operator bool() const noexcept { return false; }
|
||||
#endif // FMT_USE_LOCALE
|
||||
|
||||
public:
|
||||
@@ -1841,15 +1853,19 @@ template <typename T> class buffer {
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940
|
||||
FMT_CONSTEXPR20
|
||||
#endif
|
||||
void
|
||||
append(const U* begin, const U* end) {
|
||||
void append(const U* begin, const U* end) {
|
||||
while (begin != end) {
|
||||
auto size = size_;
|
||||
auto free_cap = capacity_ - size;
|
||||
auto count = to_unsigned(end - begin);
|
||||
try_reserve(size_ + count);
|
||||
auto free_cap = capacity_ - size_;
|
||||
if (free_cap < count) count = free_cap;
|
||||
if (free_cap < count) {
|
||||
grow_(*this, size + count);
|
||||
size = size_;
|
||||
free_cap = capacity_ - size;
|
||||
count = count < free_cap ? count : free_cap;
|
||||
}
|
||||
// A loop is faster than memcpy on small sizes.
|
||||
T* out = ptr_ + size_;
|
||||
T* out = ptr_ + size;
|
||||
for (size_t i = 0; i < count; ++i) out[i] = begin[i];
|
||||
size_ += count;
|
||||
begin += count;
|
||||
@@ -2245,8 +2261,11 @@ template <typename Context> class value {
|
||||
: pointer(const_cast<const void*>(x)) {}
|
||||
FMT_INLINE value(nullptr_t) : pointer(nullptr) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value ||
|
||||
std::is_member_pointer<T>::value)>
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(
|
||||
(std::is_pointer<T>::value ||
|
||||
std::is_member_pointer<T>::value) &&
|
||||
!std::is_void<typename std::remove_pointer<T>::type>::value)>
|
||||
value(const T&) {
|
||||
// Formatting of arbitrary pointers is disallowed. If you want to format a
|
||||
// pointer cast it to `void*` or `const void*`. In particular, this forbids
|
||||
@@ -2291,7 +2310,7 @@ template <typename Context> class value {
|
||||
template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>
|
||||
FMT_CONSTEXPR value(const T&, custom_tag) {
|
||||
// Cannot format an argument; to make type T formattable provide a
|
||||
// formatter<T> specialization: https://fmt.dev/latest/api.html#udt.
|
||||
// formatter<T> specialization: https://fmt.dev/latest/api#udt.
|
||||
type_is_unformattable_for<T, char_type> _;
|
||||
}
|
||||
|
||||
@@ -2741,7 +2760,9 @@ template <typename... T> struct fstring {
|
||||
static_assert(count<(is_view<remove_cvref_t<T>>::value &&
|
||||
std::is_reference<T>::value)...>() == 0,
|
||||
"passing views as lvalues is disallowed");
|
||||
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
|
||||
#if FMT_USE_CONSTEVAL
|
||||
parse_format_string<char>(s, checker(s, arg_pack()));
|
||||
#endif
|
||||
#ifdef FMT_ENFORCE_COMPILE_STRING
|
||||
static_assert(
|
||||
FMT_USE_CONSTEVAL && sizeof(s) != 0,
|
||||
@@ -2826,6 +2847,10 @@ using vargs =
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print("The answer is {answer}.", fmt::arg("answer", 42));
|
||||
*
|
||||
* Named arguments passed with `fmt::arg` are not supported
|
||||
* in compile-time checks, but `"answer"_a=42` are compile-time checked in
|
||||
* sufficiently new compilers. See `operator""_a()`.
|
||||
*/
|
||||
template <typename Char, typename T>
|
||||
inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {
|
||||
@@ -2983,6 +3008,7 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
|
||||
return fmt::println(stdout, fmt, static_cast<T&&>(args)...);
|
||||
}
|
||||
|
||||
FMT_PRAGMA_GCC(diagnostic pop)
|
||||
FMT_PRAGMA_CLANG(diagnostic pop)
|
||||
FMT_PRAGMA_GCC(pop_options)
|
||||
FMT_END_EXPORT
|
||||
|
||||
@@ -1594,8 +1594,13 @@ class get_locale {
|
||||
|
||||
public:
|
||||
inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
|
||||
if (localized)
|
||||
::new (&locale_) std::locale(loc.template get<std::locale>());
|
||||
if (!localized) return;
|
||||
ignore_unused(loc);
|
||||
::new (&locale_) std::locale(
|
||||
#if FMT_USE_LOCALE
|
||||
loc.template get<std::locale>()
|
||||
#endif
|
||||
);
|
||||
}
|
||||
inline ~get_locale() {
|
||||
if (has_locale_) locale_.~locale();
|
||||
|
||||
@@ -155,7 +155,7 @@ enum class color : uint32_t {
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
}; // enum class color
|
||||
|
||||
enum class terminal_color : uint8_t {
|
||||
black = 30,
|
||||
@@ -429,7 +429,7 @@ template <typename Char> struct ansi_color_escape {
|
||||
|
||||
private:
|
||||
static constexpr size_t num_emphases = 8;
|
||||
Char buffer[7u + 4u * num_emphases];
|
||||
Char buffer[7u + 4u * num_emphases] = {};
|
||||
size_t size = 0;
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
FMT_EXPORT class compiled_string {};
|
||||
class compiled_string {};
|
||||
|
||||
template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
@@ -59,6 +60,8 @@ template <detail::fixed_string Str> constexpr auto operator""_cf() {
|
||||
} // namespace literals
|
||||
#endif
|
||||
|
||||
FMT_END_EXPORT
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
@@ -519,7 +522,7 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||
fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
|
||||
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
@@ -556,8 +559,8 @@ template <size_t N> class static_format_result {
|
||||
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
|
||||
}
|
||||
|
||||
auto str() const -> fmt::string_view { return {data, N - 1}; }
|
||||
auto c_str() const -> const char* { return data; }
|
||||
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
|
||||
FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,11 +40,18 @@
|
||||
|
||||
#include "base.h"
|
||||
|
||||
// libc++ supports string_view in pre-c++17.
|
||||
#if FMT_HAS_INCLUDE(<string_view>) && \
|
||||
(FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION))
|
||||
# define FMT_USE_STRING_VIEW
|
||||
#endif
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <stdlib.h> // malloc, free
|
||||
|
||||
# include <cmath> // std::signbit
|
||||
# include <cstddef> // std::byte
|
||||
# include <cstdint> // uint32_t
|
||||
# include <cstdlib> // std::malloc, std::free
|
||||
# include <cstring> // std::memcpy
|
||||
# include <limits> // std::numeric_limits
|
||||
# include <new> // std::bad_alloc
|
||||
@@ -61,11 +68,8 @@
|
||||
# include <bit> // std::bit_cast
|
||||
# endif
|
||||
|
||||
// libc++ supports string_view in pre-c++17.
|
||||
# if FMT_HAS_INCLUDE(<string_view>) && \
|
||||
(FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION))
|
||||
# if defined(FMT_USE_STRING_VIEW)
|
||||
# include <string_view>
|
||||
# define FMT_USE_STRING_VIEW
|
||||
# endif
|
||||
|
||||
# if FMT_MSC_VERSION
|
||||
@@ -489,8 +493,8 @@ template <typename OutputIt,
|
||||
#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
|
||||
__attribute__((no_sanitize("undefined")))
|
||||
#endif
|
||||
FMT_CONSTEXPR20 inline auto
|
||||
reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* {
|
||||
FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) ->
|
||||
typename OutputIt::value_type* {
|
||||
auto& c = get_container(it);
|
||||
size_t size = c.size();
|
||||
c.resize(size + n);
|
||||
@@ -732,24 +736,20 @@ using fast_float_t = conditional_t<sizeof(T) == sizeof(double), double, float>;
|
||||
template <typename T>
|
||||
using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
|
||||
|
||||
#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
|
||||
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
|
||||
#endif
|
||||
|
||||
// An allocator that uses malloc/free to allow removing dependency on the C++
|
||||
// standard libary runtime. std::decay is used for back_inserter to be found by
|
||||
// standard library runtime. std::decay is used for back_inserter to be found by
|
||||
// ADL when applied to memory_buffer.
|
||||
template <typename T> struct allocator : private std::decay<void> {
|
||||
using value_type = T;
|
||||
|
||||
auto allocate(size_t n) -> T* {
|
||||
FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), "");
|
||||
T* p = static_cast<T*>(std::malloc(n * sizeof(T)));
|
||||
T* p = static_cast<T*>(malloc(n * sizeof(T)));
|
||||
if (!p) FMT_THROW(std::bad_alloc());
|
||||
return p;
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t) { std::free(p); }
|
||||
void deallocate(T* p, size_t) { free(p); }
|
||||
|
||||
constexpr friend auto operator==(allocator, allocator) noexcept -> bool {
|
||||
return true; // All instances of this allocator are equivalent.
|
||||
@@ -759,6 +759,14 @@ template <typename T> struct allocator : private std::decay<void> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Formatter>
|
||||
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
|
||||
-> decltype(f.set_debug_format(set)) {
|
||||
f.set_debug_format(set);
|
||||
}
|
||||
template <typename Formatter>
|
||||
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
@@ -1019,9 +1027,9 @@ using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
|
||||
(factor) * 100000, (factor) * 1000000, (factor) * 10000000, \
|
||||
(factor) * 100000000, (factor) * 1000000000
|
||||
|
||||
// Converts value in the range [0, 100) to a string.
|
||||
// GCC generates slightly better code when value is pointer-size.
|
||||
inline auto digits2(size_t value) -> const char* {
|
||||
// Converts value in the range [0, 100) to a string. GCC generates a bit better
|
||||
// code when value is pointer-size (https://www.godbolt.org/z/5fEPMT1cc).
|
||||
inline auto digits2(size_t value) noexcept -> const char* {
|
||||
// Align data since unaligned access may be slower when crossing a
|
||||
// hardware-specific boundary.
|
||||
alignas(2) static const char data[] =
|
||||
@@ -1299,7 +1307,13 @@ class utf8_to_utf16 {
|
||||
inline auto str() const -> std::wstring { return {&buffer_[0], size()}; }
|
||||
};
|
||||
|
||||
enum class to_utf8_error_policy { abort, replace };
|
||||
enum class to_utf8_error_policy { abort, replace, wtf };
|
||||
|
||||
inline void to_utf8_3bytes(buffer<char>& buf, uint32_t cp) {
|
||||
buf.push_back(static_cast<char>(0xe0 | (cp >> 12)));
|
||||
buf.push_back(static_cast<char>(0x80 | ((cp & 0xfff) >> 6)));
|
||||
buf.push_back(static_cast<char>(0x80 | (cp & 0x3f)));
|
||||
}
|
||||
|
||||
// A converter from UTF-16/UTF-32 (host endian) to UTF-8.
|
||||
template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
|
||||
@@ -1341,8 +1355,13 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
|
||||
// Handle a surrogate pair.
|
||||
++p;
|
||||
if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
|
||||
if (policy == to_utf8_error_policy::abort) return false;
|
||||
buf.append(string_view("\xEF\xBF\xBD"));
|
||||
switch (policy) {
|
||||
case to_utf8_error_policy::abort: return false;
|
||||
case to_utf8_error_policy::replace:
|
||||
buf.append(string_view("\xEF\xBF\xBD"));
|
||||
break;
|
||||
case to_utf8_error_policy::wtf: to_utf8_3bytes(buf, c); break;
|
||||
}
|
||||
--p;
|
||||
continue;
|
||||
}
|
||||
@@ -1354,9 +1373,7 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
|
||||
buf.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
|
||||
buf.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||
buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
to_utf8_3bytes(buf, c);
|
||||
} else if (c >= 0x10000 && c <= 0x10ffff) {
|
||||
buf.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||
buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||
@@ -2506,7 +2523,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
|
||||
auto grouping = Grouping(loc, specs.localized());
|
||||
size += grouping.count_separators(exp);
|
||||
return write_padded<Char, align::right>(
|
||||
out, specs, to_unsigned(size), [&](iterator it) {
|
||||
out, specs, static_cast<size_t>(size), [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
it = write_significand<Char>(it, f.significand, significand_size,
|
||||
f.exponent, grouping);
|
||||
@@ -2522,7 +2539,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
|
||||
auto grouping = Grouping(loc, specs.localized());
|
||||
size += grouping.count_separators(exp);
|
||||
return write_padded<Char, align::right>(
|
||||
out, specs, to_unsigned(size), [&](iterator it) {
|
||||
out, specs, static_cast<size_t>(size), [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
it = write_significand(it, f.significand, significand_size, exp,
|
||||
decimal_point, grouping);
|
||||
@@ -2538,7 +2555,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
|
||||
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
|
||||
size += 1 + (pointy ? 1 : 0) + num_zeros;
|
||||
return write_padded<Char, align::right>(
|
||||
out, specs, to_unsigned(size), [&](iterator it) {
|
||||
out, specs, static_cast<size_t>(size), [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
*it++ = Char('0');
|
||||
if (!pointy) return it;
|
||||
@@ -2582,7 +2599,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
|
||||
*it++ = Char(exp_char);
|
||||
return write_exponent<Char>(exp, it);
|
||||
};
|
||||
auto usize = to_unsigned(size);
|
||||
size_t usize = static_cast<size_t>(size);
|
||||
return specs.width > 0
|
||||
? write_padded<Char, align::right>(out, specs, usize, write)
|
||||
: base_iterator(out, write(reserve(out, usize)));
|
||||
@@ -4130,6 +4147,14 @@ template <typename T, typename Char = char> struct nested_formatter {
|
||||
|
||||
inline namespace literals {
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
/**
|
||||
* User-defined literal equivalent of `fmt::arg`, but with compile-time checks.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* using namespace fmt::literals;
|
||||
* fmt::print("The answer is {answer}.", "answer"_a=42);
|
||||
*/
|
||||
template <detail::fixed_string S> constexpr auto operator""_a() {
|
||||
using char_t = remove_cvref_t<decltype(*S.data)>;
|
||||
return detail::udl_arg<char_t, sizeof(S.data) / sizeof(char_t), S>();
|
||||
@@ -4236,7 +4261,11 @@ class format_int {
|
||||
* // A compile-time error because 'd' is an invalid specifier for strings.
|
||||
* std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
|
||||
*/
|
||||
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)
|
||||
#if FMT_USE_CONSTEVAL
|
||||
# define FMT_STRING(s) s
|
||||
#else
|
||||
# define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)
|
||||
#endif // FMT_USE_CONSTEVAL
|
||||
|
||||
FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args)
|
||||
-> std::system_error;
|
||||
|
||||
@@ -136,10 +136,9 @@ FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
|
||||
* **Example**:
|
||||
*
|
||||
* // This throws a system_error with the description
|
||||
* // cannot open file 'madeup': The system cannot find the file
|
||||
* specified.
|
||||
* // or similar (system message may vary).
|
||||
* const char *filename = "madeup";
|
||||
* // cannot open file 'foo': The system cannot find the file specified.
|
||||
* // or similar (system message may vary) if the file doesn't exist.
|
||||
* const char *filename = "foo";
|
||||
* LPOFSTRUCT of = LPOFSTRUCT();
|
||||
* HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
* if (file == HFILE_ERROR) {
|
||||
@@ -162,14 +161,6 @@ inline auto system_category() noexcept -> const std::error_category& {
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// std::system is not available on some platforms such as iOS (#2248).
|
||||
#ifdef __OSX__
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
void say(const S& fmt, Args&&... args) {
|
||||
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
@@ -365,17 +356,17 @@ FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
|
||||
|
||||
/// A fast buffered output stream for writing from a single thread. Writing from
|
||||
/// multiple threads without external synchronization may result in a data race.
|
||||
class FMT_API ostream : private detail::buffer<char> {
|
||||
class ostream : private detail::buffer<char> {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params);
|
||||
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
|
||||
|
||||
static void grow(buffer<char>& buf, size_t);
|
||||
FMT_API static void grow(buffer<char>& buf, size_t);
|
||||
|
||||
public:
|
||||
ostream(ostream&& other) noexcept;
|
||||
~ostream();
|
||||
FMT_API ostream(ostream&& other) noexcept;
|
||||
FMT_API ~ostream();
|
||||
|
||||
operator writer() {
|
||||
detail::buffer<char>& buf = *this;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace detail {
|
||||
namespace {
|
||||
struct file_access_tag {};
|
||||
} // namespace
|
||||
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
|
||||
template <typename Tag, typename BufType, FILE* BufType::* FileMemberPtr>
|
||||
class file_access {
|
||||
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||
};
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
|
||||
# define FMT_LIFETIMEBOUND [[clang::lifetimebound]]
|
||||
#else
|
||||
# define FMT_LIFETIMEBOUND
|
||||
#endif
|
||||
FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address")
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
FMT_EXPORT
|
||||
@@ -234,14 +241,6 @@ using range_reference_type =
|
||||
template <typename Range>
|
||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||
|
||||
template <typename Formatter>
|
||||
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
|
||||
-> decltype(f.set_debug_format(set)) {
|
||||
f.set_debug_format(set);
|
||||
}
|
||||
template <typename Formatter>
|
||||
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
|
||||
|
||||
template <typename T>
|
||||
struct range_format_kind_
|
||||
: std::integral_constant<range_format,
|
||||
@@ -821,12 +820,12 @@ auto join(Range&& r, string_view sep)
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* auto t = std::tuple<int, char>{1, 'a'};
|
||||
* auto t = std::tuple<int, char>(1, 'a');
|
||||
* fmt::print("{}", fmt::join(t, ", "));
|
||||
* // Output: 1, a
|
||||
*/
|
||||
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
|
||||
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
|
||||
FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep)
|
||||
-> tuple_join_view<Tuple, char> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
@@ -84,10 +84,12 @@ namespace detail {
|
||||
template <typename Char, typename PathChar>
|
||||
auto get_path_string(const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
|
||||
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
|
||||
else
|
||||
if constexpr (std::is_same_v<Char, char> &&
|
||||
std::is_same_v<PathChar, wchar_t>) {
|
||||
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf);
|
||||
} else {
|
||||
return p.string<Char>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
@@ -111,12 +113,17 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
||||
template <typename Char, typename OutputIt, typename T>
|
||||
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
|
||||
|
||||
template <typename Char, typename OutputIt, typename T, typename FormatContext>
|
||||
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
|
||||
-> OutputIt {
|
||||
if constexpr (has_to_string_view<T>::value)
|
||||
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
||||
return write<Char>(out, v);
|
||||
|
||||
formatter<std::remove_cv_t<T>, Char> underlying;
|
||||
maybe_set_debug_format(underlying, true);
|
||||
return underlying.format(v, ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -139,50 +146,39 @@ template <typename Variant, typename Char> class is_variant_formattable {
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
|
||||
template <typename OutputIt>
|
||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
size_t size = 0;
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = demangled_name_ptr.get();
|
||||
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* begin = demangled_name_ptr.get();
|
||||
char* to = begin + 5; // std::
|
||||
for (char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
|
||||
char* begin) -> string_view {
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* to = begin + 5; // std::
|
||||
for (const char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
const char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
*to++ = *from++;
|
||||
}
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
*to++ = *from++;
|
||||
}
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
}
|
||||
return detail::write_bytes<char>(out, demangled_name_view);
|
||||
# elif FMT_MSC_VERSION
|
||||
const string_view demangled_name(ti.name());
|
||||
return demangled_name_view;
|
||||
}
|
||||
|
||||
template <class OutputIt>
|
||||
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
|
||||
-> OutputIt {
|
||||
const string_view demangled_name(abi_name_view);
|
||||
for (size_t i = 0; i < demangled_name.size(); ++i) {
|
||||
auto sub = demangled_name;
|
||||
sub.remove_prefix(i);
|
||||
@@ -201,6 +197,39 @@ auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
size_t size = 0;
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = normalize_libcxx_inline_namespaces(
|
||||
demangled_name_ptr.get(), demangled_name_ptr.get());
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
return detail::write_bytes<char>(out, demangled_name_view);
|
||||
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
|
||||
return normalize_msvc_abi_name(ti.name(), out);
|
||||
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
|
||||
const string_view demangled_name = ti.name();
|
||||
std::string name_copy(demangled_name.size(), '\0');
|
||||
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
|
||||
// front of types
|
||||
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
|
||||
name_copy.end());
|
||||
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
|
||||
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
|
||||
// environments, we need to eliminate both of them.
|
||||
const string_view normalized_name =
|
||||
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
|
||||
return detail::write_bytes<char>(out, normalized_name);
|
||||
# else
|
||||
return detail::write_bytes<char>(out, string_view(ti.name()));
|
||||
# endif
|
||||
@@ -255,21 +284,6 @@ template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
class path : public std::filesystem::path {
|
||||
public:
|
||||
auto display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{}"), base);
|
||||
}
|
||||
auto system_string() const -> std::string { return string(); }
|
||||
|
||||
auto generic_display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{:g}"), base);
|
||||
}
|
||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||
};
|
||||
|
||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
@@ -319,6 +333,21 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
class path : public std::filesystem::path {
|
||||
public:
|
||||
auto display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{}"), base);
|
||||
}
|
||||
auto system_string() const -> std::string { return string(); }
|
||||
|
||||
auto generic_display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{:g}"), base);
|
||||
}
|
||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||
};
|
||||
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
template <size_t N, typename Char>
|
||||
@@ -353,25 +382,16 @@ template <typename T, typename Char>
|
||||
struct formatter<std::optional<T>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||
private:
|
||||
formatter<T, Char> underlying_;
|
||||
formatter<std::remove_cv_t<T>, Char> underlying_;
|
||||
static constexpr basic_string_view<Char> optional =
|
||||
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
||||
'('>{};
|
||||
static constexpr basic_string_view<Char> none =
|
||||
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
|
||||
-> decltype(u.set_debug_format(set)) {
|
||||
u.set_debug_format(set);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
|
||||
maybe_set_debug_format(underlying_, true);
|
||||
detail::maybe_set_debug_format(underlying_, true);
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
@@ -407,10 +427,10 @@ struct formatter<std::expected<T, E>, Char,
|
||||
if (value.has_value()) {
|
||||
out = detail::write<Char>(out, "expected(");
|
||||
if constexpr (!std::is_void<T>::value)
|
||||
out = detail::write_escaped_alternative<Char>(out, *value);
|
||||
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
|
||||
} else {
|
||||
out = detail::write<Char>(out, "unexpected(");
|
||||
out = detail::write_escaped_alternative<Char>(out, value.error());
|
||||
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
|
||||
}
|
||||
*out++ = ')';
|
||||
return out;
|
||||
@@ -474,7 +494,7 @@ struct formatter<Variant, Char,
|
||||
FMT_TRY {
|
||||
std::visit(
|
||||
[&](const auto& v) {
|
||||
out = detail::write_escaped_alternative<Char>(out, v);
|
||||
out = detail::write_escaped_alternative<Char>(out, v, ctx);
|
||||
},
|
||||
value);
|
||||
}
|
||||
@@ -495,6 +515,8 @@ template <> struct formatter<std::error_code> {
|
||||
bool debug_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end) return it;
|
||||
@@ -625,6 +647,11 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||
};
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
||||
template <typename T> struct is_tuple_like;
|
||||
|
||||
template <typename T>
|
||||
struct is_tuple_like<std::complex<T>> : std::false_type {};
|
||||
|
||||
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
||||
private:
|
||||
detail::dynamic_format_specs<Char> specs_;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#if FMT_USE_LOCALE
|
||||
template FMT_API locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
|
||||
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
# A script to invoke mkdocs with the correct environment.
|
||||
# Additionally supports deploying via mike:
|
||||
# ./mkdocs deploy [mike-deploy-options]
|
||||
# For example:
|
||||
# ./mkdocs deploy <version>
|
||||
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
|
||||
# <version> there.
|
||||
|
||||
import errno, os, shutil, sys
|
||||
from subprocess import call
|
||||
@@ -40,7 +44,7 @@ config_path = os.path.join(support_dir, 'mkdocs.yml')
|
||||
args = sys.argv[1:]
|
||||
if len(args) > 0:
|
||||
command = args[0]
|
||||
if command == 'deploy':
|
||||
if command == 'deploy' or command == 'set-default':
|
||||
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
|
||||
site_repo = git_url + 'fmtlib/fmt.dev.git'
|
||||
|
||||
@@ -64,14 +68,18 @@ if len(args) > 0:
|
||||
if ret != 0 or version == 'dev':
|
||||
sys.exit(ret)
|
||||
current_doc_path = os.path.join(site_dir, version)
|
||||
os.makedirs(current_doc_path, exist_ok=True)
|
||||
redirect_page_path = os.path.join(current_doc_path, 'api.html')
|
||||
with open(redirect_page_path, "w") as file:
|
||||
file.write(redirect_page)
|
||||
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
|
||||
# mike stages files added by deploy for deletion for unclear reason,
|
||||
# undo it.
|
||||
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
|
||||
if False:
|
||||
os.makedirs(current_doc_path, exist_ok=True)
|
||||
redirect_page_path = os.path.join(current_doc_path, 'api.html')
|
||||
with open(redirect_page_path, "w") as file:
|
||||
file.write(redirect_page)
|
||||
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
|
||||
sys.exit(ret)
|
||||
elif not command.startswith('-'):
|
||||
args += ['-f', config_path]
|
||||
|
||||
@@ -2,190 +2,274 @@
|
||||
# Copyright (c) 2012 - present, Victor Zverovich
|
||||
# https://github.com/fmtlib/fmt/blob/master/LICENSE
|
||||
|
||||
# pyright: strict
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
|
||||
from typing import Any, List, Mapping, Optional
|
||||
|
||||
from mkdocstrings.handlers.base import BaseHandler
|
||||
from markupsafe import Markup
|
||||
from mkdocstrings import BaseHandler
|
||||
from typing_extensions import TYPE_CHECKING, Any, ClassVar, final, override
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
from mkdocstrings import CollectorItem, HandlerOptions
|
||||
|
||||
|
||||
@final
|
||||
class Definition:
|
||||
"""A definition extracted by Doxygen."""
|
||||
|
||||
def __init__(self, name: str, kind: Optional[str] = None,
|
||||
node: Optional[ElementTree.Element] = None,
|
||||
is_member: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
kind: "str | None" = None,
|
||||
node: "ET.Element | None" = None,
|
||||
is_member: bool = False,
|
||||
):
|
||||
self.name = name
|
||||
self.kind = kind if kind is not None else node.get('kind')
|
||||
self.desc = None
|
||||
self.id = name if not is_member else None
|
||||
self.members = None
|
||||
self.params = None
|
||||
self.template_params = None
|
||||
self.trailing_return_type = None
|
||||
self.type = None
|
||||
self.kind: "str | None" = None
|
||||
if kind is not None:
|
||||
self.kind = kind
|
||||
elif node is not None:
|
||||
self.kind = node.get("kind")
|
||||
self.desc: "list[ET.Element[str]] | None" = None
|
||||
self.id: "str | None" = name if not is_member else None
|
||||
self.members: "list[Definition] | None" = None
|
||||
self.params: "list[Definition] | None" = None
|
||||
self.template_params: "list[Definition] | None" = None
|
||||
self.trailing_return_type: "str | None" = None
|
||||
self.type: "str | None" = None
|
||||
|
||||
|
||||
# A map from Doxygen to HTML tags.
|
||||
tag_map = {
|
||||
'bold': 'b',
|
||||
'emphasis': 'em',
|
||||
'computeroutput': 'code',
|
||||
'para': 'p',
|
||||
'programlisting': 'pre',
|
||||
'verbatim': 'pre'
|
||||
"bold": "b",
|
||||
"emphasis": "em",
|
||||
"computeroutput": "code",
|
||||
"para": "p",
|
||||
"itemizedlist": "ul",
|
||||
"listitem": "li",
|
||||
}
|
||||
|
||||
# A map from Doxygen tags to text.
|
||||
tag_text_map = {
|
||||
'codeline': '',
|
||||
'highlight': '',
|
||||
'sp': ' '
|
||||
}
|
||||
tag_text_map = {"codeline": "", "highlight": "", "sp": " "}
|
||||
|
||||
|
||||
def escape_html(s: str) -> str:
|
||||
return s.replace("<", "<")
|
||||
|
||||
|
||||
def doxyxml2html(nodes: List[ElementTree.Element]):
|
||||
out = ''
|
||||
for n in nodes:
|
||||
tag = tag_map.get(n.tag)
|
||||
if not tag:
|
||||
out += tag_text_map[n.tag]
|
||||
out += '<' + tag + '>' if tag else ''
|
||||
out += '<code class="language-cpp">' if tag == 'pre' else ''
|
||||
if n.text:
|
||||
out += escape_html(n.text)
|
||||
out += doxyxml2html(list(n))
|
||||
out += '</code>' if tag == 'pre' else ''
|
||||
out += '</' + tag + '>' if tag else ''
|
||||
if n.tail:
|
||||
out += n.tail
|
||||
# Converts a node from doxygen to HTML format.
|
||||
def convert_node(
|
||||
node: ET.Element, tag: str, attrs: "Mapping[str, str] | None" = None
|
||||
) -> str:
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
|
||||
out: str = "<" + tag
|
||||
for key, value in attrs.items():
|
||||
out += " " + key + '="' + value + '"'
|
||||
out += ">"
|
||||
if node.text:
|
||||
out += escape_html(node.text)
|
||||
out += doxyxml2html(list(node))
|
||||
out += "</" + tag + ">"
|
||||
if node.tail:
|
||||
out += node.tail
|
||||
return out
|
||||
|
||||
|
||||
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
|
||||
template_param_list = node.find('templateparamlist')
|
||||
def doxyxml2html(nodes: "list[ET.Element]"):
|
||||
out = ""
|
||||
for n in nodes:
|
||||
tag = tag_map.get(n.tag)
|
||||
if tag:
|
||||
out += convert_node(n, tag)
|
||||
continue
|
||||
if n.tag == "programlisting" or n.tag == "verbatim":
|
||||
out += "<pre>"
|
||||
out += convert_node(n, "code", {"class": "language-cpp"})
|
||||
out += "</pre>"
|
||||
continue
|
||||
if n.tag == "ulink":
|
||||
out += convert_node(n, "a", {"href": n.attrib["url"]})
|
||||
continue
|
||||
out += tag_text_map[n.tag]
|
||||
return out
|
||||
|
||||
|
||||
def convert_template_params(node: ET.Element) -> "list[Definition] | None":
|
||||
template_param_list = node.find("templateparamlist")
|
||||
if template_param_list is None:
|
||||
return None
|
||||
params = []
|
||||
for param_node in template_param_list.findall('param'):
|
||||
name = param_node.find('declname')
|
||||
param = Definition(name.text if name is not None else '', 'param')
|
||||
param.type = param_node.find('type').text
|
||||
params: "list[Definition]" = []
|
||||
for param_node in template_param_list.findall("param"):
|
||||
name = param_node.find("declname")
|
||||
if name is not None:
|
||||
name = name.text
|
||||
if name is None:
|
||||
name = ""
|
||||
param = Definition(name, "param")
|
||||
param_type = param_node.find("type")
|
||||
if param_type is not None:
|
||||
param.type = param_type.text
|
||||
params.append(param)
|
||||
return params
|
||||
|
||||
|
||||
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
|
||||
return node.findall('briefdescription/para') + \
|
||||
node.findall('detaileddescription/para')
|
||||
def get_description(node: ET.Element) -> list[ET.Element]:
|
||||
return node.findall("briefdescription/para") + node.findall(
|
||||
"detaileddescription/para"
|
||||
)
|
||||
|
||||
|
||||
def normalize_type(type_: str) -> str:
|
||||
type_ = type_.replace('< ', '<').replace(' >', '>')
|
||||
return type_.replace(' &', '&').replace(' *', '*')
|
||||
type_ = type_.replace("< ", "<").replace(" >", ">")
|
||||
return type_.replace(" &", "&").replace(" *", "*")
|
||||
|
||||
|
||||
def convert_type(type_: ElementTree.Element) -> Optional[str]:
|
||||
def convert_type(type_: "ET.Element | None") -> "str | None":
|
||||
if type_ is None:
|
||||
return None
|
||||
result = type_.text if type_.text else ''
|
||||
result = type_.text if type_.text else ""
|
||||
for ref in type_:
|
||||
if ref.text is None:
|
||||
raise ValueError
|
||||
result += ref.text
|
||||
if ref.tail:
|
||||
result += ref.tail
|
||||
if type_.tail is None:
|
||||
raise ValueError
|
||||
result += type_.tail.strip()
|
||||
return normalize_type(result)
|
||||
|
||||
|
||||
def convert_params(func: ElementTree.Element) -> List[Definition]:
|
||||
params = []
|
||||
for p in func.findall('param'):
|
||||
d = Definition(p.find('declname').text, 'param')
|
||||
d.type = convert_type(p.find('type'))
|
||||
def convert_params(func: ET.Element) -> list[Definition]:
|
||||
params: "list[Definition]" = []
|
||||
for p in func.findall("param"):
|
||||
declname = p.find("declname")
|
||||
if declname is None or declname.text is None:
|
||||
raise ValueError
|
||||
d = Definition(declname.text, "param")
|
||||
d.type = convert_type(p.find("type"))
|
||||
params.append(d)
|
||||
return params
|
||||
|
||||
|
||||
def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
|
||||
def convert_return_type(d: Definition, node: ET.Element) -> None:
|
||||
d.trailing_return_type = None
|
||||
if d.type == 'auto' or d.type == 'constexpr auto':
|
||||
parts = node.find('argsstring').text.split(' -> ')
|
||||
if d.type == "auto" or d.type == "constexpr auto":
|
||||
argsstring = node.find("argsstring")
|
||||
if argsstring is None or argsstring.text is None:
|
||||
raise ValueError
|
||||
parts = argsstring.text.split(" -> ")
|
||||
if len(parts) > 1:
|
||||
d.trailing_return_type = normalize_type(parts[1])
|
||||
|
||||
|
||||
def render_param(param: Definition) -> str:
|
||||
return param.type + (f' {param.name}' if len(param.name) > 0 else '')
|
||||
if param.type is None:
|
||||
raise ValueError
|
||||
return param.type + (f" {param.name}" if len(param.name) > 0 else "")
|
||||
|
||||
|
||||
def render_decl(d: Definition) -> str:
|
||||
text = ''
|
||||
text = ""
|
||||
if d.id is not None:
|
||||
text += f'<a id="{d.id}">\n'
|
||||
text += '<pre><code class="language-cpp decl">'
|
||||
|
||||
text += '<div>'
|
||||
text += "<div>"
|
||||
if d.template_params is not None:
|
||||
text += 'template <'
|
||||
text += ', '.join([render_param(p) for p in d.template_params])
|
||||
text += '>\n'
|
||||
text += '</div>'
|
||||
text += "template <"
|
||||
text += ", ".join([render_param(p) for p in d.template_params])
|
||||
text += ">\n"
|
||||
text += "</div>"
|
||||
|
||||
text += '<div>'
|
||||
end = ';'
|
||||
if d.kind == 'function' or d.kind == 'variable':
|
||||
text += d.type + ' ' if len(d.type) > 0 else ''
|
||||
elif d.kind == 'typedef':
|
||||
text += 'using '
|
||||
elif d.kind == 'define':
|
||||
end = ''
|
||||
text += "<div>"
|
||||
end = ";"
|
||||
if d.kind is None:
|
||||
raise ValueError
|
||||
if d.kind == "function" or d.kind == "variable":
|
||||
if d.type is None:
|
||||
raise ValueError
|
||||
text += d.type + " " if len(d.type) > 0 else ""
|
||||
elif d.kind == "typedef":
|
||||
text += "using "
|
||||
elif d.kind == "define":
|
||||
end = ""
|
||||
else:
|
||||
text += d.kind + ' '
|
||||
text += d.kind + " "
|
||||
text += d.name
|
||||
|
||||
if d.params is not None:
|
||||
params = ', '.join([
|
||||
(p.type + ' ' if p.type else '') + p.name for p in d.params])
|
||||
text += '(' + escape_html(params) + ')'
|
||||
params = ", ".join([
|
||||
(p.type + " " if p.type else "") + p.name for p in d.params
|
||||
])
|
||||
text += "(" + escape_html(params) + ")"
|
||||
if d.trailing_return_type:
|
||||
text += ' -⁠> ' + escape_html(d.trailing_return_type)
|
||||
elif d.kind == 'typedef':
|
||||
text += ' = ' + escape_html(d.type)
|
||||
text += " -⁠> " + escape_html(d.trailing_return_type)
|
||||
elif d.kind == "typedef":
|
||||
if d.type is None:
|
||||
raise ValueError
|
||||
text += " = " + escape_html(d.type)
|
||||
|
||||
text += end
|
||||
text += '</div>'
|
||||
text += '</code></pre>\n'
|
||||
text += "</div>"
|
||||
text += "</code></pre>\n"
|
||||
if d.id is not None:
|
||||
text += f'</a>\n'
|
||||
text += "</a>\n"
|
||||
return text
|
||||
|
||||
|
||||
@final
|
||||
class CxxHandler(BaseHandler):
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(handler='cxx', **kwargs)
|
||||
name: ClassVar[str] = "cxx"
|
||||
|
||||
domain: ClassVar[str] = "cxx"
|
||||
|
||||
def __init__(
|
||||
self, config: "Mapping[str, Any]", base_dir: Path, **kwargs: Any
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.config = config
|
||||
"""The handler configuration."""
|
||||
self.base_dir = base_dir
|
||||
"""The base directory of the project."""
|
||||
|
||||
headers = [
|
||||
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
|
||||
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
|
||||
"args.h",
|
||||
"base.h",
|
||||
"chrono.h",
|
||||
"color.h",
|
||||
"compile.h",
|
||||
"format.h",
|
||||
"os.h",
|
||||
"ostream.h",
|
||||
"printf.h",
|
||||
"ranges.h",
|
||||
"std.h",
|
||||
"xchar.h",
|
||||
]
|
||||
|
||||
# Run doxygen.
|
||||
cmd = ['doxygen', '-']
|
||||
cmd = ["doxygen", "-"]
|
||||
support_dir = Path(__file__).parents[3]
|
||||
top_dir = os.path.dirname(support_dir)
|
||||
include_dir = os.path.join(top_dir, 'include', 'fmt')
|
||||
self._ns2doxyxml = {}
|
||||
build_dir = os.path.join(top_dir, 'build')
|
||||
include_dir = os.path.join(top_dir, "include", "fmt")
|
||||
self._ns2doxyxml: "dict[str, ET.ElementTree[ET.Element[str]]]" = {}
|
||||
build_dir = os.path.join(top_dir, "build")
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
|
||||
self._doxyxml_dir = os.path.join(build_dir, "doxyxml")
|
||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
||||
_, _ = p.communicate(input=r'''
|
||||
_, _ = p.communicate(
|
||||
input=r"""
|
||||
PROJECT_NAME = fmt
|
||||
GENERATE_XML = YES
|
||||
GENERATE_LATEX = NO
|
||||
@@ -205,18 +289,20 @@ class CxxHandler(BaseHandler):
|
||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||
"FMT_END_NAMESPACE=}}" \
|
||||
"FMT_DOC=1"
|
||||
'''.format(
|
||||
' '.join([os.path.join(include_dir, h) for h in headers]),
|
||||
self._doxyxml_dir).encode('utf-8'))
|
||||
""".format(
|
||||
" ".join([os.path.join(include_dir, h) for h in headers]),
|
||||
self._doxyxml_dir,
|
||||
).encode("utf-8")
|
||||
)
|
||||
if p.returncode != 0:
|
||||
raise CalledProcessError(p.returncode, cmd)
|
||||
|
||||
# Merge all file-level XMLs into one to simplify search.
|
||||
self._file_doxyxml = None
|
||||
self._file_doxyxml: "ET.ElementTree[ET.Element[str]] | None" = None
|
||||
for h in headers:
|
||||
filename = h.replace(".h", "_8h.xml")
|
||||
with open(os.path.join(self._doxyxml_dir, filename)) as f:
|
||||
doxyxml = ElementTree.parse(f)
|
||||
doxyxml = ET.parse(f)
|
||||
if self._file_doxyxml is None:
|
||||
self._file_doxyxml = doxyxml
|
||||
continue
|
||||
@@ -224,33 +310,43 @@ class CxxHandler(BaseHandler):
|
||||
for node in doxyxml.getroot():
|
||||
root.append(node)
|
||||
|
||||
def collect_compound(self, identifier: str,
|
||||
cls: List[ElementTree.Element]) -> Definition:
|
||||
def collect_compound(self, identifier: str, cls: "list[ET.Element]") -> Definition:
|
||||
"""Collect a compound definition such as a struct."""
|
||||
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
|
||||
refid = cls[0].get("refid")
|
||||
if refid is None:
|
||||
raise ValueError
|
||||
path = os.path.join(self._doxyxml_dir, refid + ".xml")
|
||||
with open(path) as f:
|
||||
xml = ElementTree.parse(f)
|
||||
node = xml.find('compounddef')
|
||||
xml = ET.parse(f)
|
||||
node = xml.find("compounddef")
|
||||
if node is None:
|
||||
raise ValueError
|
||||
d = Definition(identifier, node=node)
|
||||
d.template_params = convert_template_params(node)
|
||||
d.desc = get_description(node)
|
||||
d.members = []
|
||||
for m in \
|
||||
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
|
||||
node.findall('sectiondef[@kind="public-func"]/memberdef'):
|
||||
name = m.find('name').text
|
||||
for m in node.findall(
|
||||
'sectiondef[@kind="public-attrib"]/memberdef'
|
||||
) + node.findall('sectiondef[@kind="public-func"]/memberdef'):
|
||||
name = m.find("name")
|
||||
if name is None or name.text is None:
|
||||
raise ValueError
|
||||
name = name.text
|
||||
# Doxygen incorrectly classifies members of private unnamed unions as
|
||||
# public members of the containing class.
|
||||
if name.endswith('_'):
|
||||
if name.endswith("_"):
|
||||
continue
|
||||
desc = get_description(m)
|
||||
if len(desc) == 0:
|
||||
continue
|
||||
kind = m.get('kind')
|
||||
member = Definition(name if name else '', kind=kind, is_member=True)
|
||||
type_text = m.find('type').text
|
||||
member.type = type_text if type_text else ''
|
||||
if kind == 'function':
|
||||
kind = m.get("kind")
|
||||
member = Definition(name if name else "", kind=kind, is_member=True)
|
||||
type_ = m.find("type")
|
||||
if type_ is None:
|
||||
raise ValueError
|
||||
type_text = type_.text
|
||||
member.type = type_text if type_text else ""
|
||||
if kind == "function":
|
||||
member.params = convert_params(m)
|
||||
convert_return_type(member, m)
|
||||
member.template_params = None
|
||||
@@ -258,48 +354,60 @@ class CxxHandler(BaseHandler):
|
||||
d.members.append(member)
|
||||
return d
|
||||
|
||||
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
|
||||
qual_name = 'fmt::' + identifier
|
||||
@override
|
||||
def collect(self, identifier: str, options: "Mapping[str, Any]") -> Definition:
|
||||
qual_name = "fmt::" + identifier
|
||||
|
||||
param_str = None
|
||||
paren = qual_name.find('(')
|
||||
paren = qual_name.find("(")
|
||||
if paren > 0:
|
||||
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
|
||||
qual_name, param_str = qual_name[:paren], qual_name[paren + 1 : -1]
|
||||
|
||||
colons = qual_name.rfind('::')
|
||||
namespace, name = qual_name[:colons], qual_name[colons + 2:]
|
||||
colons = qual_name.rfind("::")
|
||||
namespace, name = qual_name[:colons], qual_name[colons + 2 :]
|
||||
|
||||
# Load XML.
|
||||
doxyxml = self._ns2doxyxml.get(namespace)
|
||||
if doxyxml is None:
|
||||
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
|
||||
path = f"namespace{namespace.replace('::', '_1_1')}.xml"
|
||||
with open(os.path.join(self._doxyxml_dir, path)) as f:
|
||||
doxyxml = ElementTree.parse(f)
|
||||
doxyxml = ET.parse(f)
|
||||
self._ns2doxyxml[namespace] = doxyxml
|
||||
|
||||
nodes = doxyxml.findall(
|
||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||
nodes = doxyxml.findall(f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||
if len(nodes) == 0:
|
||||
if self._file_doxyxml is None:
|
||||
raise ValueError
|
||||
nodes = self._file_doxyxml.findall(
|
||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||
candidates = []
|
||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/.."
|
||||
)
|
||||
candidates: "list[str]" = []
|
||||
for node in nodes:
|
||||
# Process a function or a typedef.
|
||||
params = None
|
||||
params: "list[Definition] | None" = None
|
||||
d = Definition(name, node=node)
|
||||
if d.kind == 'function':
|
||||
if d.kind == "function":
|
||||
params = convert_params(node)
|
||||
node_param_str = ', '.join([p.type for p in params])
|
||||
params_type: "list[str]" = []
|
||||
for p in params:
|
||||
if p.type is None:
|
||||
raise ValueError
|
||||
else:
|
||||
params_type.append(p.type)
|
||||
node_param_str = ", ".join(params_type)
|
||||
if param_str and param_str != node_param_str:
|
||||
candidates.append(f'{name}({node_param_str})')
|
||||
candidates.append(f"{name}({node_param_str})")
|
||||
continue
|
||||
elif d.kind == 'define':
|
||||
elif d.kind == "define":
|
||||
params = []
|
||||
for p in node.findall('param'):
|
||||
param = Definition(p.find('defname').text, kind='param')
|
||||
for p in node.findall("param"):
|
||||
defname = p.find("defname")
|
||||
if defname is None or defname.text is None:
|
||||
raise ValueError
|
||||
param = Definition(defname.text, kind="param")
|
||||
param.type = None
|
||||
params.append(param)
|
||||
d.type = convert_type(node.find('type'))
|
||||
d.type = convert_type(node.find("type"))
|
||||
d.template_params = convert_template_params(node)
|
||||
d.params = params
|
||||
convert_return_type(d, node)
|
||||
@@ -308,31 +416,42 @@ class CxxHandler(BaseHandler):
|
||||
|
||||
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
|
||||
if not cls:
|
||||
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
|
||||
raise Exception(f"Cannot find {identifier}. Candidates: {candidates}")
|
||||
return self.collect_compound(identifier, cls)
|
||||
|
||||
def render(self, d: Definition, config: dict) -> str:
|
||||
@override
|
||||
def render(
|
||||
self,
|
||||
data: "CollectorItem",
|
||||
options: "HandlerOptions",
|
||||
*,
|
||||
locale: "str | None" = None,
|
||||
) -> str:
|
||||
d = data
|
||||
if d.id is not None:
|
||||
self.do_heading('', 0, id=d.id)
|
||||
_ = self.do_heading(Markup(), 0, id=d.id)
|
||||
if d.desc is None:
|
||||
raise ValueError
|
||||
text = '<div class="docblock">\n'
|
||||
text += render_decl(d)
|
||||
text += '<div class="docblock-desc">\n'
|
||||
text += doxyxml2html(d.desc)
|
||||
if d.members is not None:
|
||||
for m in d.members:
|
||||
text += self.render(m, config)
|
||||
text += '</div>\n'
|
||||
text += '</div>\n'
|
||||
text += self.render(m, options, locale=locale)
|
||||
text += "</div>\n"
|
||||
text += "</div>\n"
|
||||
return text
|
||||
|
||||
|
||||
def get_handler(theme: str, custom_templates: Optional[str] = None,
|
||||
**_config: Any) -> CxxHandler:
|
||||
def get_handler(
|
||||
handler_config: "MutableMapping[str, Any]", tool_config: "MkDocsConfig", **kwargs: Any
|
||||
) -> CxxHandler:
|
||||
"""Return an instance of `CxxHandler`.
|
||||
|
||||
Arguments:
|
||||
theme: The theme to use when rendering contents.
|
||||
custom_templates: Directory containing custom templates.
|
||||
**_config: Configuration passed to the handler.
|
||||
handler_config: The handler configuration.
|
||||
tool_config: The tool (SSG) configuration.
|
||||
"""
|
||||
return CxxHandler(theme=theme, custom_templates=custom_templates)
|
||||
base_dir = Path(tool_config.config_file_path or "./mkdocs.yml").parent
|
||||
return CxxHandler(config=handler_config, base_dir=base_dir, **kwargs)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
add_subdirectory(gtest)
|
||||
|
||||
include(CheckSymbolExists)
|
||||
|
||||
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
||||
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
||||
target_include_directories(test-main PUBLIC
|
||||
@@ -62,21 +60,10 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||
endif ()
|
||||
add_fmt_test(ostream-test)
|
||||
add_fmt_test(compile-test)
|
||||
add_fmt_test(compile-fp-test)
|
||||
if (MSVC)
|
||||
# Without this option, MSVC returns 199711L for the __cplusplus macro.
|
||||
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
|
||||
endif()
|
||||
add_fmt_test(printf-test)
|
||||
add_fmt_test(ranges-test ranges-odr-test.cc)
|
||||
add_fmt_test(no-builtin-types-test HEADER_ONLY)
|
||||
|
||||
add_fmt_test(scan-test HEADER_ONLY)
|
||||
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
|
||||
if (HAVE_STRPTIME)
|
||||
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
|
||||
endif ()
|
||||
|
||||
add_fmt_test(std-test)
|
||||
try_compile(compile_result_unused
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
|
||||
@@ -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
|
||||
@@ -90,9 +90,6 @@ TEST(compile_test, format_escape) {
|
||||
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
|
||||
}
|
||||
|
||||
TEST(compile_test, format_wide_string) {
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
|
||||
}
|
||||
|
||||
TEST(compile_test, format_specs) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
||||
@@ -124,7 +121,6 @@ TEST(compile_test, manual_ordering) {
|
||||
"true 42 42 foo 0x1234 foo",
|
||||
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
|
||||
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
|
||||
}
|
||||
|
||||
TEST(compile_test, named) {
|
||||
@@ -133,10 +129,6 @@ TEST(compile_test, named) {
|
||||
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
|
||||
fmt::detail::runtime_named_field<char>>);
|
||||
|
||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
|
||||
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
|
||||
fmt::arg("arg", 43)));
|
||||
|
||||
EXPECT_EQ("foobar",
|
||||
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
|
||||
fmt::arg("a1", "bar")));
|
||||
@@ -318,7 +310,6 @@ TEST(compile_test, compile_format_string_literal) {
|
||||
using namespace fmt::literals;
|
||||
EXPECT_EQ("", fmt::format(""_cf));
|
||||
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
|
||||
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -426,6 +417,40 @@ TEST(compile_time_formatting_test, custom_type) {
|
||||
TEST(compile_time_formatting_test, multibyte_fill) {
|
||||
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
|
||||
}
|
||||
|
||||
TEST(compile_time_formatting_test, floating_point) {
|
||||
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
|
||||
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
|
||||
|
||||
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
|
||||
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
|
||||
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
|
||||
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
|
||||
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
|
||||
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
|
||||
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
|
||||
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
|
||||
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
|
||||
|
||||
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
|
||||
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
|
||||
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
|
||||
EXPECT_EQ("9223372036854775808.000000",
|
||||
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
|
||||
|
||||
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
|
||||
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
|
||||
if (std::signbit(-nan))
|
||||
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
|
||||
else
|
||||
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
||||
|
||||
constexpr double inf = std::numeric_limits<double>::infinity();
|
||||
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
|
||||
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
|
||||
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_USE_CONSTEXPR_STRING
|
||||
|
||||
@@ -2036,11 +2036,6 @@ TEST(format_test, unpacked_args) {
|
||||
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
|
||||
}
|
||||
|
||||
constexpr char with_null[3] = {'{', '}', '\0'};
|
||||
constexpr char no_null[2] = {'{', '}'};
|
||||
static constexpr char static_with_null[3] = {'{', '}', '\0'};
|
||||
static constexpr char static_no_null[2] = {'{', '}'};
|
||||
|
||||
TEST(format_test, compile_time_string) {
|
||||
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
|
||||
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
|
||||
@@ -2055,19 +2050,12 @@ TEST(format_test, compile_time_string) {
|
||||
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
|
||||
#endif
|
||||
|
||||
(void)static_with_null;
|
||||
(void)static_no_null;
|
||||
static constexpr char format_str[3] = {'{', '}', '\0'};
|
||||
(void)format_str;
|
||||
#ifndef _MSC_VER
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42");
|
||||
#endif
|
||||
|
||||
(void)with_null;
|
||||
(void)no_null;
|
||||
#if FMT_CPLUSPLUS >= 201703L
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
|
||||
#endif
|
||||
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
|
||||
#endif
|
||||
|
||||
@@ -12,10 +12,10 @@ void invoke_inner(fmt::string_view format_str, Rep rep) {
|
||||
auto value = std::chrono::duration<Rep, Period>(rep);
|
||||
try {
|
||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||
std::string message = fmt::format(format_str, value);
|
||||
std::string message = fmt::format(fmt::runtime(format_str), value);
|
||||
#else
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buf), format_str, value);
|
||||
fmt::format_to(std::back_inserter(buf), fmt::runtime(format_str), value);
|
||||
#endif
|
||||
} catch (std::exception&) {
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
||||
|
||||
// The size of the largest possible type in use.
|
||||
// To let the the fuzzer mutation be efficient at cross pollinating between
|
||||
// To let the fuzzer mutation be efficient at cross pollinating between
|
||||
// different types, use a fixed size format. The same bit pattern, interpreted
|
||||
// as another type, is likely interesting.
|
||||
constexpr auto fixed_size = 16;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#------------------------------------------------------------------------------
|
||||
# Build the google test library
|
||||
|
||||
# We compile Google Test ourselves instead of using pre-compiled libraries.
|
||||
# Compile Google Test ourselves instead of using pre-compiled libraries.
|
||||
# See the Google Test FAQ "Why is it not recommended to install a
|
||||
# pre-compiled copy of Google Test (for example, into /usr/local)?"
|
||||
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
|
||||
@@ -19,14 +19,10 @@ else ()
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
||||
# Disable MSVC warnings about _CRT_INSECURE_DEPRECATE functions.
|
||||
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# Disable MSVC warnings of POSIX functions.
|
||||
# Disable MSVC warnings about POSIX functions.
|
||||
target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Silence MSVC tr1 deprecation warning in gmock.
|
||||
target_compile_definitions(gtest
|
||||
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if !defined(__GNUC__) || __GNUC__ >= 5
|
||||
#if !defined(__GNUC__) || (__GNUC__ >= 5 || defined(__clang__))
|
||||
|
||||
# define FMT_BUILTIN_TYPES 0
|
||||
# include "fmt/format.h"
|
||||
# include "fmt/compile.h"
|
||||
|
||||
TEST(no_builtin_types_test, format) {
|
||||
EXPECT_EQ(fmt::format("{}", 42), "42");
|
||||
@@ -22,4 +23,9 @@ TEST(no_builtin_types_test, double_is_custom_type) {
|
||||
EXPECT_EQ(fmt::format_args(args).get(0).type(),
|
||||
fmt::detail::type::custom_type);
|
||||
}
|
||||
|
||||
TEST(no_builtin_types_test, format_pointer_compiled) {
|
||||
const void* p = nullptr;
|
||||
fmt::format(FMT_COMPILE("{:} {}"), 42, p);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -265,7 +265,7 @@ template <> struct formatter<abstract> : ostream_formatter {};
|
||||
} // namespace fmt
|
||||
|
||||
void format_abstract_compiles(const abstract& a) {
|
||||
fmt::format(FMT_COMPILE("{}"), a);
|
||||
(void)fmt::format(FMT_COMPILE("{}"), a);
|
||||
}
|
||||
|
||||
TEST(ostream_test, is_formattable) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fmt/os.h" // fmt::system_category
|
||||
#include "fmt/ranges.h"
|
||||
#include "gtest-extra.h" // StartsWith
|
||||
|
||||
#ifdef __cpp_lib_filesystem
|
||||
@@ -38,13 +39,12 @@ TEST(std_test, path) {
|
||||
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
|
||||
L"\x0447\x044B\x043D\x0430")),
|
||||
"Шчучыншчына");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD <20> TAIL");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")),
|
||||
"HEAD \xF0\x9F\x98\x80 TAIL");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")),
|
||||
"HEAD <20>\xF0\x9F\x98\x80 TAIL");
|
||||
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
|
||||
"[\xED\xA0\xBD\xF0\x9F\x98\x80]");
|
||||
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
|
||||
# endif
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ TEST(std_test, optional) {
|
||||
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
||||
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
|
||||
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
|
||||
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -196,7 +197,33 @@ class my_class {
|
||||
return fmt::to_string(elm.av);
|
||||
}
|
||||
};
|
||||
|
||||
class my_class_int {
|
||||
public:
|
||||
int av;
|
||||
|
||||
private:
|
||||
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
|
||||
};
|
||||
} // namespace my_nso
|
||||
|
||||
TEST(std_test, expected_format_as) {
|
||||
#ifdef __cpp_lib_expected
|
||||
EXPECT_EQ(
|
||||
fmt::format(
|
||||
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
|
||||
"expected(\"first\")");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{}",
|
||||
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
|
||||
"expected(\"7\")");
|
||||
EXPECT_EQ(fmt::format("{}",
|
||||
std::expected<my_nso::my_class_int, int>{
|
||||
my_nso::my_class_int{8}}),
|
||||
"expected(8)");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(std_test, optional_format_as) {
|
||||
#ifdef __cpp_lib_optional
|
||||
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
|
||||
@@ -205,6 +232,8 @@ TEST(std_test, optional_format_as) {
|
||||
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
|
||||
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
|
||||
"optional(\"7\")");
|
||||
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
|
||||
"optional(8)");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -274,6 +303,24 @@ TEST(std_test, variant) {
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(std_test, variant_format_as) {
|
||||
#ifdef __cpp_lib_variant
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
|
||||
"variant(\"first\")");
|
||||
EXPECT_EQ(fmt::format(
|
||||
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
|
||||
"variant(\"first\")");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
|
||||
"variant(\"7\")");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{}",
|
||||
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
|
||||
"variant(8)");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(std_test, error_code) {
|
||||
auto& generic = std::generic_category();
|
||||
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
|
||||
@@ -288,6 +335,10 @@ TEST(std_test, error_code) {
|
||||
EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
|
||||
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
|
||||
"\"generic:42\"");
|
||||
EXPECT_EQ(fmt::format("{}",
|
||||
std::map<std::error_code, int>{
|
||||
{std::error_code(42, generic), 0}}),
|
||||
"{\"generic:42\": 0}");
|
||||
}
|
||||
|
||||
template <typename Catch> void exception_test() {
|
||||
|
||||
Reference in New Issue
Block a user