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