forked from fmtlib/fmt
Compare commits
436 Commits
9.1.0
...
esp-idf-re
Author | SHA1 | Date | |
---|---|---|---|
c43fe02f66 | |||
f5e54359df | |||
a003ab93cf | |||
9b74160817 | |||
a21690bdfa | |||
c9efd8968c | |||
e786824053 | |||
2b20d7be6f | |||
1f38ebbdb1 | |||
e418179694 | |||
0bffed8957 | |||
cc077a5e3b | |||
a992b3d1fc | |||
af1b768cc7 | |||
2a7c45b54e | |||
bd868f3a51 | |||
bbb784fb1b | |||
6c845f57e5 | |||
a379595c5f | |||
9dfde78714 | |||
e92a705bdc | |||
5a866fe852 | |||
e150ea0cc2 | |||
29ce2ff8a8 | |||
2e1362addb | |||
e57ca2e368 | |||
4c56612c67 | |||
e1acd5f4d9 | |||
6ffee2f752 | |||
ee475d6409 | |||
ecc9141259 | |||
d424862319 | |||
3c1b3337de | |||
35fb69ebe0 | |||
f92be35c09 | |||
7a2f6ac210 | |||
744ff55549 | |||
70ae48b005 | |||
ebb10347c4 | |||
1005720169 | |||
aeb6ad4dd0 | |||
96d1fa22d4 | |||
8a4bec5cf5 | |||
eacd51c249 | |||
757564f5cd | |||
f4214ae8dd | |||
aecf80d304 | |||
503d49286d | |||
4f46cb82f3 | |||
3dec65b7fd | |||
dbabb305c3 | |||
ac0ab8eff3 | |||
40f35d6f04 | |||
31c2c5679e | |||
b2728a3170 | |||
77e0b0e228 | |||
e475859042 | |||
436c131d4c | |||
388bc296b7 | |||
95e1ea5737 | |||
fb97cb2318 | |||
dd5a9691f9 | |||
72dc4491ea | |||
9bea6ec04a | |||
661b23edeb | |||
8e87d3a8be | |||
a474916560 | |||
de4705f84d | |||
e4c8cfe38e | |||
606f85f8b2 | |||
a331dbfb65 | |||
13156e54bf | |||
9158bea1e1 | |||
dd17f89a16 | |||
1daae555b3 | |||
6ad301235f | |||
8732ad8773 | |||
5afb1821a9 | |||
a81135f2c8 | |||
dfc34821ad | |||
0765e7284c | |||
977d887a4e | |||
c86fe0b8d3 | |||
5dbe0ff954 | |||
de0757b578 | |||
8fe893c0ac | |||
0f823df46a | |||
60fd9941c0 | |||
8abfc145be | |||
686b3353aa | |||
b2106f3639 | |||
35547d6003 | |||
179c7e5a66 | |||
61fb3a15ff | |||
bd393456ed | |||
6be36af0d4 | |||
2a35eeed8a | |||
256a826d63 | |||
6369af37d3 | |||
0b8404918e | |||
171a020c82 | |||
d8f04e3995 | |||
5e988f8dfa | |||
19b17618a9 | |||
c684a06d51 | |||
abdb7fdf88 | |||
8b09fe2a0a | |||
adad18a74d | |||
858e528abd | |||
a54cb108d4 | |||
ef55d4f52e | |||
70b6a6fa44 | |||
6fe895410d | |||
d0652d225f | |||
de8d0171a2 | |||
7401fe046a | |||
08ef0d0842 | |||
d60b907f87 | |||
4ce086f731 | |||
821f8cdb45 | |||
0bf6ed7e1d | |||
e40e04face | |||
0a1c27281a | |||
2f605cc896 | |||
1d54499ac0 | |||
3c6053c535 | |||
4a392adaa7 | |||
b14913fae5 | |||
2117df299c | |||
616a493786 | |||
9a034b0d55 | |||
e0fc0e85e3 | |||
552c43aba9 | |||
d6846f4ac8 | |||
aeedac5884 | |||
eaa6307691 | |||
e82bf41a1a | |||
e077396f5a | |||
861facad0a | |||
75bfe57614 | |||
697e76ba30 | |||
a425e0ff3b | |||
c36dd825ba | |||
130b8fcdb9 | |||
a47e8419be | |||
ea49c91cd1 | |||
d7592ad8bf | |||
ebfb2e6779 | |||
5780269d57 | |||
b471192160 | |||
8f18e72df5 | |||
93d7cb12f1 | |||
0e4278717b | |||
93a30a0746 | |||
a08196b149 | |||
0398ba42ca | |||
a0b8a92e3d | |||
5cf2342aa2 | |||
fe9d39d7cb | |||
4c98561979 | |||
403b271ed7 | |||
2c991e1af6 | |||
c984df9815 | |||
fbf21ed224 | |||
575583144e | |||
e7f6888c7a | |||
39db2dfd06 | |||
9b7829e264 | |||
1e0ce567ef | |||
dde8cf3bb7 | |||
e84b00e014 | |||
b12ffea4fb | |||
f61f15cc5b | |||
192df93d7b | |||
d8973bf16b | |||
d7a8e50cb5 | |||
02cae7e48a | |||
53162142b2 | |||
5bcf0d7f97 | |||
f8c9fabd94 | |||
62ff4e1dbd | |||
f449ca0525 | |||
eafcd3c8e1 | |||
18154cc903 | |||
0de789cf29 | |||
c039389223 | |||
93e81bb5d8 | |||
e7d6eb6794 | |||
18e7a2532b | |||
0489c19dcb | |||
8ec94ac6a5 | |||
d97d8cea67 | |||
d8a2698e6c | |||
d9c19940a3 | |||
4b5ae0b0ef | |||
75f3b1c094 | |||
faf83406a9 | |||
165814d57a | |||
33f7150778 | |||
c98e5a08a4 | |||
119c6bd16f | |||
77eeb71830 | |||
13bf99f9db | |||
1d0257e4c0 | |||
4613d48fd3 | |||
4a4a2a2bd6 | |||
fce74caa15 | |||
02bf4d1c1c | |||
466e0650ec | |||
029caa8ea2 | |||
e406ddbfaf | |||
9095679536 | |||
7f46cb75b8 | |||
4e3f381058 | |||
d3c10f5167 | |||
ab956f600f | |||
97aedeab48 | |||
bce8d4ed08 | |||
a91c7b286d | |||
19c074e477 | |||
41cfc739fe | |||
f6276a2c2b | |||
6002ddf825 | |||
6549ffde8e | |||
d9bc5f1320 | |||
9c5cd998d1 | |||
93bfa05382 | |||
d8e1c4265a | |||
e1720c0e51 | |||
7f882918eb | |||
cbc7b8d5c1 | |||
050293646f | |||
3daf33837c | |||
e0748e61dd | |||
b94e1016fa | |||
98699719f8 | |||
48dfbcaa95 | |||
c644c753d7 | |||
73b7cee7fb | |||
5b8302079d | |||
3a69529e8b | |||
76f520835f | |||
507c3042d8 | |||
1741e90dec | |||
d646fd0daf | |||
b5c2f74f45 | |||
e03753c4ac | |||
6e6eb63770 | |||
3c5464ba1c | |||
655046d24f | |||
581c6292c9 | |||
7718eeeacc | |||
44e0eea94e | |||
99070899b7 | |||
05e3a9233a | |||
70db193f09 | |||
a2c05a10ec | |||
cae9bf45b9 | |||
87c066a35b | |||
9409b2e4d8 | |||
f89cd276f7 | |||
240b728d81 | |||
dfbb952b2c | |||
39971eb336 | |||
0f42c17d85 | |||
bfc0924eac | |||
676c2a107e | |||
2c80cedc39 | |||
dda53082be | |||
2622cd23e6 | |||
9e4a54fa6e | |||
9ce6480676 | |||
9121f9b1d3 | |||
b7535365b2 | |||
09ed1ddb9c | |||
0ec65d99aa | |||
71e4e02722 | |||
aad546baa5 | |||
14a69fcc54 | |||
bf34ffd33f | |||
6056e07125 | |||
aa99b86409 | |||
6ade2eb4e5 | |||
caa6974942 | |||
a73a9b6a84 | |||
72785a3aba | |||
0c3dd5ddd7 | |||
739b600f40 | |||
3710c4d38f | |||
a05ba44df8 | |||
ffb9b1d13c | |||
32190859ec | |||
8fe4d97d5e | |||
7e5a959564 | |||
9e60304869 | |||
7ad48c1f65 | |||
a921a596e7 | |||
3e762fdf5c | |||
79981a2528 | |||
bd12aaa98e | |||
b8f36207c9 | |||
d907786f04 | |||
f2355bbe5e | |||
f398c94761 | |||
4841784e82 | |||
cb72c23e9e | |||
583f2d8209 | |||
32c4390704 | |||
3a5e19fbf5 | |||
dfb857ebef | |||
9ea9b6bcb1 | |||
2b0ff62a7f | |||
d1745084e0 | |||
407e7b7b6d | |||
3cf9794755 | |||
934c8e5f76 | |||
fc96938345 | |||
f0ab112c34 | |||
9660e5b956 | |||
a585571e90 | |||
840ec8569d | |||
1dadeb8a33 | |||
275b4b3417 | |||
e004f1d699 | |||
bde1a6070d | |||
040dc2a5d4 | |||
6a186bcd66 | |||
8c56919bd2 | |||
115001a3b1 | |||
b90895412f | |||
d072f1dc69 | |||
3999fd193a | |||
c06e0b4ede | |||
1bf302a4ea | |||
f1733afd49 | |||
f61dcccc6e | |||
f9bcbdcbcf | |||
1a854b4aa5 | |||
62ceb181b1 | |||
b0c8263cb2 | |||
d24be2e95c | |||
8d50d814db | |||
115ca96e0e | |||
886491625d | |||
74c51ff37e | |||
69ffedfe52 | |||
fae6f7e081 | |||
a69e43c9d7 | |||
91c024ed33 | |||
649aa102d6 | |||
31364732dc | |||
74d55a4938 | |||
8276f1a204 | |||
81ebe70b9b | |||
3160847ebd | |||
6a95f8c7eb | |||
c7980542d3 | |||
7df30f91ae | |||
d2e89c8b08 | |||
bd19593204 | |||
fd0d0ec8df | |||
8e93434edd | |||
fc07217d85 | |||
cb7373b469 | |||
795ed8abf5 | |||
66d71a1b35 | |||
80f8d34427 | |||
64965bdc96 | |||
e1ffa7655d | |||
8c19bf3f2f | |||
f67dbc9811 | |||
cd7202e039 | |||
51d3685efe | |||
9254cfa6f0 | |||
cfb34a0607 | |||
5ad7b71381 | |||
d2c47c0df2 | |||
491c32cbd9 | |||
662adf4f33 | |||
ad91cab374 | |||
0ccaed3a6c | |||
ad719619cc | |||
6e0a5f7fba | |||
48f525d025 | |||
0b5cb18b71 | |||
4c4f99a583 | |||
3272a7a3ce | |||
a48e3355a6 | |||
afcf424294 | |||
ac85afaab6 | |||
3178bb9a26 | |||
cf58f64c54 | |||
e4e0ae3918 | |||
d65acc4e6c | |||
c9f790b061 | |||
6b8144a5ac | |||
2d66ad5d33 | |||
042af53324 | |||
192859c2b5 | |||
e2f6d7665b | |||
61844b6b67 | |||
7a752e75ff | |||
94ceb38a09 | |||
58c4c012fa | |||
c3494ae364 | |||
8ae56161c8 | |||
76705fc2ee | |||
21c2137e77 | |||
ecffca6726 | |||
3176e0fad7 | |||
1feb430faa | |||
b98ffb7dbd | |||
bac53951b8 | |||
d59b89e9cd | |||
58a5563a9f | |||
1b94271ff6 | |||
768d79a839 | |||
91ecb38a34 | |||
aec3bb5d0a | |||
29c6000137 | |||
fec5515c55 | |||
f187274d36 | |||
fc5e59fe4a | |||
d6a8704605 | |||
56c72a671c | |||
4191477b98 | |||
75383a87f9 | |||
48327a82e3 | |||
b79ed4105a | |||
64e29893cf | |||
0b0f7cfbfc | |||
40e414d823 | |||
33b4c33c5b | |||
a07411c2b9 | |||
797d82b21a | |||
a553521d6d |
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # Necessary to update action hashs
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
# Allow up to 3 opened pull requests for github-actions versions
|
||||
open-pull-requests-limit: 3
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@ -1,6 +1,7 @@
|
||||
<!--
|
||||
Please read the contribution guidelines before submitting a pull request:
|
||||
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
|
||||
By submitting this pull request, you agree that your contributions are licensed
|
||||
under the {fmt} license, and agree to future changes to the licensing.
|
||||
By submitting this pull request, you agree to license your contribution(s)
|
||||
under the terms outlined in LICENSE.rst and represent that you have the right
|
||||
to do so.
|
||||
-->
|
||||
|
30
.github/workflows/cifuzz.yml
vendored
Normal file
30
.github/workflows/cifuzz.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: CIFuzz
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'fmt'
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Run Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'fmt'
|
||||
fuzz-seconds: 300
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
10
.github/workflows/doc.yml
vendored
10
.github/workflows/doc.yml
vendored
@ -11,7 +11,15 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Add ubuntu mirrors
|
||||
run: |
|
||||
# Github Actions caching proxy is at times unreliable
|
||||
# see https://github.com/actions/runner-images/issues/7048
|
||||
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
|
||||
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
|
||||
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
|
||||
|
||||
- name: Create Build Environment
|
||||
run: |
|
||||
|
43
.github/workflows/linux.yml
vendored
43
.github/workflows/linux.yml
vendored
@ -7,66 +7,83 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
cxx: [g++-4.8, g++-10, clang++-9]
|
||||
build_type: [Debug, Release]
|
||||
std: [11]
|
||||
os: [ubuntu-18.04]
|
||||
include:
|
||||
- cxx: g++-4.8
|
||||
install: sudo apt install g++-4.8
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-8
|
||||
build_type: Debug
|
||||
std: 14
|
||||
install: sudo apt install g++-8
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-8
|
||||
build_type: Debug
|
||||
std: 17
|
||||
install: sudo apt install g++-8
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-9
|
||||
build_type: Debug
|
||||
std: 17
|
||||
- cxx: g++-10
|
||||
build_type: Debug
|
||||
std: 17
|
||||
os: ubuntu-18.04
|
||||
- cxx: g++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
install: sudo apt install g++-11
|
||||
- cxx: clang++-8
|
||||
build_type: Debug
|
||||
std: 17
|
||||
cxxflags: -stdlib=libc++
|
||||
os: ubuntu-18.04
|
||||
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
|
||||
- cxx: clang++-9
|
||||
install: sudo apt install clang-9
|
||||
- cxx: clang++-9
|
||||
build_type: Debug
|
||||
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||
std: 17
|
||||
os: ubuntu-18.04
|
||||
install: sudo apt install clang-9
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
cxxflags: -stdlib=libc++
|
||||
os: ubuntu-20.04
|
||||
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
||||
- shared: -DBUILD_SHARED_LIBS=ON
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Set timezone
|
||||
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
|
||||
|
||||
- name: Add repositories for older GCC
|
||||
run: |
|
||||
# Below two repos provide GCC 4.8, 5.5 and 6.4
|
||||
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main'
|
||||
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe'
|
||||
# Below two repos additionally update GCC 6 to 6.5
|
||||
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main'
|
||||
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe'
|
||||
if: ${{ matrix.cxx == 'g++-4.8' }}
|
||||
|
||||
- name: Add ubuntu mirrors
|
||||
run: |
|
||||
# Github Actions caching proxy is at times unreliable
|
||||
# see https://github.com/actions/runner-images/issues/7048
|
||||
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
|
||||
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
|
||||
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
|
||||
|
||||
- name: Create Build Environment
|
||||
run: |
|
||||
${{matrix.install}}
|
||||
sudo apt update
|
||||
${{matrix.install}}
|
||||
sudo apt install locales-all
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
|
19
.github/workflows/macos.yml
vendored
19
.github/workflows/macos.yml
vendored
@ -7,15 +7,29 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-11, macos-13]
|
||||
build_type: [Debug, Release]
|
||||
std: [11, 17, 20]
|
||||
exclude:
|
||||
- { os: macos-11, std: 20 }
|
||||
- { os: macos-13, std: 11 }
|
||||
- { os: macos-13, std: 17 }
|
||||
include:
|
||||
- shared: -DBUILD_SHARED_LIBS=ON
|
||||
|
||||
runs-on: '${{ matrix.os }}'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Set timezone
|
||||
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
|
||||
|
||||
- name: Select Xcode 14.3 (macOS 13)
|
||||
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
@ -24,6 +38,7 @@ jobs:
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||
|
||||
|
65
.github/workflows/scorecard.yml
vendored
Normal file
65
.github/workflows/scorecard.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||
# by a third-party and are governed by separate terms of service, privacy
|
||||
# policy, and support documentation.
|
||||
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '26 14 * * 5'
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
16
.github/workflows/windows.yml
vendored
16
.github/workflows/windows.yml
vendored
@ -41,7 +41,10 @@ jobs:
|
||||
standard: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Set timezone
|
||||
run: tzutil /s "Ekaterinburg Standard Time"
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
@ -75,14 +78,17 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
strategy:
|
||||
matrix:
|
||||
sys: [ mingw64, mingw32, ucrt64 ]
|
||||
sys: [ mingw64, ucrt64 ]
|
||||
steps:
|
||||
- uses: msys2/setup-msys2@v2
|
||||
- name: Set timezone
|
||||
run: tzutil /s "Ekaterinburg Standard Time"
|
||||
shell: cmd
|
||||
- uses: msys2/setup-msys2@7efe20baefed56359985e327d329042cde2434ff # v2
|
||||
with:
|
||||
release: false
|
||||
msystem: ${{matrix.sys}}
|
||||
pacboy: cc:p cmake:p ninja:p lld:p
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Configure
|
||||
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
env: { LDFLAGS: -fuse-ld=lld }
|
||||
@ -90,3 +96,5 @@ jobs:
|
||||
run: cmake --build ../build
|
||||
- name: Test
|
||||
run: ctest -j `nproc` --test-dir ../build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
||||
|
149
CMakeLists.txt
149
CMakeLists.txt
@ -1,4 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
if(NOT DEFINED IDF_TARGET)
|
||||
cmake_minimum_required(VERSION 3.8...3.26)
|
||||
|
||||
# Fallback for using newer policies on CMake <3.12.
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||
@ -24,15 +25,86 @@ function(join result_var)
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# DEPRECATED! Should be merged into add_module_library.
|
||||
function(enable_module target)
|
||||
if (MSVC)
|
||||
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||
target_compile_options(${target}
|
||||
PRIVATE /interface /ifcOutput ${BMI}
|
||||
INTERFACE /reference fmt=${BMI})
|
||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||
endif ()
|
||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||
endfunction()
|
||||
|
||||
# Adds a library compiled with C++20 module support.
|
||||
# `enabled` is a CMake variables that specifies if modules are enabled.
|
||||
# If modules are disabled `add_module_library` falls back to creating a
|
||||
# non-modular library.
|
||||
#
|
||||
# Usage:
|
||||
# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled])
|
||||
function(add_module_library name)
|
||||
cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN})
|
||||
set(sources ${AML_UNPARSED_ARGUMENTS})
|
||||
|
||||
add_library(${name})
|
||||
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX)
|
||||
|
||||
if (NOT ${${AML_IF}})
|
||||
# Create a non-modular library.
|
||||
target_sources(${name} PRIVATE ${AML_FALLBACK})
|
||||
return()
|
||||
endif ()
|
||||
|
||||
# Modules require C++20.
|
||||
target_compile_features(${name} PUBLIC cxx_std_20)
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(${name} PUBLIC -fmodules-ts)
|
||||
endif ()
|
||||
|
||||
# `std` is affected by CMake options and may be higher than C++20.
|
||||
get_target_property(std ${name} CXX_STANDARD)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(pcms)
|
||||
foreach (src ${sources})
|
||||
get_filename_component(pcm ${src} NAME_WE)
|
||||
set(pcm ${pcm}.pcm)
|
||||
|
||||
# Propagate -fmodule-file=*.pcm to targets that link with this library.
|
||||
target_compile_options(
|
||||
${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm})
|
||||
|
||||
# Use an absolute path to prevent target_link_libraries prepending -l
|
||||
# to it.
|
||||
set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm})
|
||||
add_custom_command(
|
||||
OUTPUT ${pcm}
|
||||
COMMAND ${CMAKE_CXX_COMPILER}
|
||||
-std=c++${std} -x c++-module --precompile -c
|
||||
-o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src}
|
||||
"-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>"
|
||||
# Required by the -I generator expression above.
|
||||
COMMAND_EXPAND_LISTS
|
||||
DEPENDS ${src})
|
||||
endforeach ()
|
||||
|
||||
# Add .pcm files as sources to make sure they are built before the library.
|
||||
set(sources)
|
||||
foreach (pcm ${pcms})
|
||||
get_filename_component(pcm_we ${pcm} NAME_WE)
|
||||
set(obj ${pcm_we}.o)
|
||||
# Use an absolute path to prevent target_link_libraries prepending -l.
|
||||
set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj})
|
||||
add_custom_command(
|
||||
OUTPUT ${obj}
|
||||
COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS>
|
||||
-c -o ${obj} ${pcm}
|
||||
DEPENDS ${pcm})
|
||||
endforeach ()
|
||||
endif ()
|
||||
target_sources(${name} PRIVATE ${sources})
|
||||
endfunction()
|
||||
|
||||
include(CMakeParseArguments)
|
||||
@ -75,7 +147,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||
|
||||
# Options that control generation of various targets.
|
||||
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_INSTALL "Generate the install target." ON)
|
||||
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
@ -83,16 +155,6 @@ option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
||||
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
||||
# msvc 16.10-pre4
|
||||
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
endif ()
|
||||
if (NOT FMT_CAN_MODULE)
|
||||
set(FMT_MODULE OFF)
|
||||
message(STATUS "Module support is disabled.")
|
||||
endif ()
|
||||
if (FMT_TEST AND FMT_MODULE)
|
||||
# The tests require {fmt} to be compiled as traditional library
|
||||
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||
@ -101,6 +163,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
|
||||
if (FMT_SYSTEM_HEADERS)
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
||||
endif ()
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
|
||||
set(FMT_TEST OFF)
|
||||
message(STATUS "MSDOS is incompatible with gtest")
|
||||
endif()
|
||||
|
||||
# Get version from core.h
|
||||
file(READ include/fmt/core.h core_h)
|
||||
@ -118,23 +184,15 @@ message(STATUS "Version: ${FMT_VERSION}")
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(JoinPaths)
|
||||
|
||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||
if (${index} GREATER -1)
|
||||
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
||||
# compatibility with older CMake versions.
|
||||
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
||||
endif ()
|
||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||
|
||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||
"Preset for the export of private symbols")
|
||||
@ -220,16 +278,18 @@ endfunction()
|
||||
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
||||
xchar.h)
|
||||
if (FMT_MODULE)
|
||||
set(FMT_SOURCES src/fmt.cc)
|
||||
elseif (FMT_OS)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
else()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
if (FMT_OS)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} src/os.cc)
|
||||
endif ()
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_module_library(fmt src/fmt.cc FALLBACK
|
||||
${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst
|
||||
IF FMT_MODULE)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
if (FMT_MODULE)
|
||||
enable_module(fmt)
|
||||
endif ()
|
||||
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
||||
@ -237,11 +297,12 @@ endif ()
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (FMT_MODULE)
|
||||
enable_module(fmt)
|
||||
endif ()
|
||||
|
||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
target_compile_features(fmt PUBLIC cxx_std_11)
|
||||
else ()
|
||||
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
|
||||
endif ()
|
||||
|
||||
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
@ -262,7 +323,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||
target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
|
||||
endif ()
|
||||
if (FMT_SAFE_DURATION_CAST)
|
||||
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
||||
@ -272,7 +333,7 @@ add_library(fmt-header-only INTERFACE)
|
||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
|
||||
|
||||
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
@ -294,7 +355,7 @@ if (FMT_INSTALL)
|
||||
"Installation directory for libraries, a relative path that "
|
||||
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING
|
||||
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||
"absolute path.")
|
||||
@ -324,7 +385,6 @@ if (FMT_INSTALL)
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
||||
FRAMEWORK DESTINATION "."
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||
@ -339,8 +399,6 @@ if (FMT_INSTALL)
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||
NAMESPACE fmt::)
|
||||
|
||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
||||
endif ()
|
||||
|
||||
@ -385,3 +443,12 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
|
||||
include(CPack)
|
||||
endif ()
|
||||
else()
|
||||
idf_component_register(
|
||||
SRCS
|
||||
src/format.cc
|
||||
INCLUDE_DIRS
|
||||
include
|
||||
)
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 23)
|
||||
endif()
|
||||
|
669
ChangeLog.rst
669
ChangeLog.rst
@ -1,3 +1,669 @@
|
||||
10.1.1 - 2023-08-26
|
||||
-------------------
|
||||
|
||||
* Added formatters for ``std::atomic`` and ``atomic_flag``
|
||||
(`#3574 <https://github.com/fmtlib/fmt/pull/3574>`_,
|
||||
`#3594 <https://github.com/fmtlib/fmt/pull/3594>`_).
|
||||
Thanks `@wangzw (Zhanwei Wang) <https://github.com/wangzw>`_ and
|
||||
`@AlexGuteniev (Alex Guteniev) <https://github.com/AlexGuteniev>`_.
|
||||
|
||||
* Fixed an error about partial specialization of ``formatter<string>``
|
||||
after instantiation when compiled with gcc and C++20
|
||||
(`#3584 <https://github.com/fmtlib/fmt/issues/3584>`_).
|
||||
|
||||
* Fixed compilation as a C++20 module with gcc and clang
|
||||
(`#3587 <https://github.com/fmtlib/fmt/issues/3587>`_,
|
||||
`#3597 <https://github.com/fmtlib/fmt/pull/3597>`_,
|
||||
`#3605 <https://github.com/fmtlib/fmt/pull/3605>`_). Thanks
|
||||
`@MathewBensonCode (Mathew Benson) <https://github.com/MathewBensonCode>`_.
|
||||
|
||||
* Made ``fmt::to_string`` work with types that have ``format_as`` overloads
|
||||
(`#3575 <https://github.com/fmtlib/fmt/pull/3575>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Made ``formatted_size`` work with integral format specifiers at compile time
|
||||
(`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_).
|
||||
Thanks `@elbeno (Ben Deane) <https://github.com/elbeno>`_.
|
||||
|
||||
* Fixed a warning about the ``no_unique_address`` attribute on clang-cl
|
||||
(`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_).
|
||||
Thanks `@lukester1975 <https://github.com/lukester1975>`_.
|
||||
|
||||
* Improved compatibility with the legacy GBK encoding
|
||||
(`#3598 <https://github.com/fmtlib/fmt/issues/3598>`_,
|
||||
`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_).
|
||||
Thanks `@YuHuanTin <https://github.com/YuHuanTin>`_.
|
||||
|
||||
* Added OpenSSF Scorecard analysis
|
||||
(`#3530 <https://github.com/fmtlib/fmt/issues/3530>`_,
|
||||
`#3571 <https://github.com/fmtlib/fmt/pull/3571>`_).
|
||||
Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_.
|
||||
|
||||
* Updated CI dependencies
|
||||
(`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_,
|
||||
`#3592 <https://github.com/fmtlib/fmt/pull/3592>`_,
|
||||
`#3593 <https://github.com/fmtlib/fmt/pull/3593>`_,
|
||||
`#3602 <https://github.com/fmtlib/fmt/pull/3602>`_).
|
||||
|
||||
10.1.0 - 2023-08-12
|
||||
-------------------
|
||||
|
||||
* Optimized format string compilation resulting in up to 40% speed up in
|
||||
compiled ``format_to`` and ~4x speed up in compiled ``format_to_n`` on a
|
||||
concatenation benchmark (`#3133 <https://github.com/fmtlib/fmt/issues/3133>`_,
|
||||
`#3484 <https://github.com/fmtlib/fmt/issues/3484>`_).
|
||||
|
||||
{fmt} 10.0::
|
||||
|
||||
---------------------------------------------------------
|
||||
Benchmark Time CPU Iterations
|
||||
---------------------------------------------------------
|
||||
BM_format_to 78.9 ns 78.9 ns 8881746
|
||||
BM_format_to_n 568 ns 568 ns 1232089
|
||||
|
||||
{fmt} 10.1::
|
||||
|
||||
---------------------------------------------------------
|
||||
Benchmark Time CPU Iterations
|
||||
---------------------------------------------------------
|
||||
BM_format_to 54.9 ns 54.9 ns 12727944
|
||||
BM_format_to_n 133 ns 133 ns 5257795
|
||||
|
||||
* Optimized storage of an empty allocator in ``basic_memory_buffer``
|
||||
(`#3485 <https://github.com/fmtlib/fmt/pull/3485>`_).
|
||||
Thanks `@Minty-Meeo <https://github.com/Minty-Meeo>`_.
|
||||
|
||||
* Added formatters for proxy references to elements of ``std::vector<bool>`` and
|
||||
``std::bitset<N>`` (`#3567 <https://github.com/fmtlib/fmt/issues/3567>`_,
|
||||
`#3570 <https://github.com/fmtlib/fmt/pull/3570>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/zYb79Pvn8>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <vector>
|
||||
#include <fmt/std.h>
|
||||
|
||||
int main() {
|
||||
auto v = std::vector<bool>{true};
|
||||
fmt::print("{}", v[0]);
|
||||
}
|
||||
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
|
||||
`@felix642 (Félix-Antoine Constantin) <https://github.com/felix642>`_.
|
||||
|
||||
* Fixed an ambiguous formatter specialization for containers that look like
|
||||
container adaptors such as ``boost::flat_set``
|
||||
(`#3556 <https://github.com/fmtlib/fmt/issues/3556>`_,
|
||||
`#3561 <https://github.com/fmtlib/fmt/pull/3561>`_).
|
||||
Thanks `@5chmidti <https://github.com/5chmidti>`_.
|
||||
|
||||
* Fixed compilation when formatting durations not convertible from
|
||||
``std::chrono::seconds`` (`#3430 <https://github.com/fmtlib/fmt/pull/3430>`_).
|
||||
Thanks `@patlkli (Patrick Geltinger) <https://github.com/patlkli>`_.
|
||||
|
||||
* Made the ``formatter`` specialization for ``char*`` const-correct
|
||||
(`#3432 <https://github.com/fmtlib/fmt/pull/3432>`_).
|
||||
Thanks `@timsong-cpp <https://github.com/timsong-cpp>`_.
|
||||
|
||||
* Made ``{}`` and ``{:}`` handled consistently during compile-time checks
|
||||
(`#3526 <https://github.com/fmtlib/fmt/issues/3526>`_).
|
||||
|
||||
* Disallowed passing temporaries to ``make_format_args`` to improve API safety
|
||||
by preventing dangling references.
|
||||
|
||||
* Improved the compile-time error for unformattable types
|
||||
(`#3478 <https://github.com/fmtlib/fmt/pull/3478>`_).
|
||||
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||
|
||||
* Improved the floating-point formatter
|
||||
(`#3448 <https://github.com/fmtlib/fmt/pull/3448>`_,
|
||||
`#3450 <https://github.com/fmtlib/fmt/pull/3450>`_).
|
||||
Thanks `@florimond-collette (Florimond Collette)
|
||||
<https://github.com/florimond-collette>`_.
|
||||
|
||||
* Fixed handling of precision for ``long double`` larger than 64 bits.
|
||||
(`#3539 <https://github.com/fmtlib/fmt/issues/3539>`_,
|
||||
`#3564 <https://github.com/fmtlib/fmt/issues/3564>`_).
|
||||
|
||||
* Made floating-point and chrono tests less platform-dependent
|
||||
(`#3337 <https://github.com/fmtlib/fmt/issues/3337>`_,
|
||||
`#3433 <https://github.com/fmtlib/fmt/issues/3433>`_,
|
||||
`#3434 <https://github.com/fmtlib/fmt/pull/3434>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Removed the remnants of the Grisu floating-point formatter that has been
|
||||
replaced by Dragonbox in earlier versions.
|
||||
|
||||
* Added ``throw_format_error`` to the public API
|
||||
(`#3551 <https://github.com/fmtlib/fmt/pull/3551>`_).
|
||||
Thanks `@mjerabek (Martin Jeřábek) <https://github.com/mjerabek>`_.
|
||||
|
||||
* Made ``FMT_THROW`` assert even if assertions are disabled when compiling with
|
||||
exceptions disabled (`#3418 <https://github.com/fmtlib/fmt/issues/3418>`_,
|
||||
`#3439 <https://github.com/fmtlib/fmt/pull/3439>`_).
|
||||
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||
|
||||
* Made ``format_as`` and ``std::filesystem::path`` formatter work with exotic
|
||||
code unit types.
|
||||
(`#3457 <https://github.com/fmtlib/fmt/pull/3457>`_,
|
||||
`#3476 <https://github.com/fmtlib/fmt/pull/3476>`_).
|
||||
Thanks `@gix (Nico Rieck) <https://github.com/gix>`_,
|
||||
`@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
|
||||
|
||||
* Added support for the ``?`` format specifier to ``std::filesystem::path`` and
|
||||
made the default unescaped for consistency with strings.
|
||||
|
||||
* Deprecated the wide stream overload of ``printf``.
|
||||
|
||||
* Removed unused ``basic_printf_parse_context``.
|
||||
|
||||
* Improved RTTI detection used when formatting exceptions
|
||||
(`#3468 <https://github.com/fmtlib/fmt/pull/3468>`_).
|
||||
Thanks `@danakj (Dana Jansens) <https://github.com/danakj>`_.
|
||||
|
||||
* Improved compatibility with VxWorks7
|
||||
(`#3467 <https://github.com/fmtlib/fmt/pull/3467>`_).
|
||||
Thanks `@wenshan1 (Bin Lan) <https://github.com/wenshan1>`_.
|
||||
|
||||
* Improved documentation
|
||||
(`#3174 <https://github.com/fmtlib/fmt/issues/3174>`_,
|
||||
`#3423 <https://github.com/fmtlib/fmt/issues/3423>`_,
|
||||
`#3454 <https://github.com/fmtlib/fmt/pull/3454>`_,
|
||||
`#3458 <https://github.com/fmtlib/fmt/issues/3458>`_,
|
||||
`#3461 <https://github.com/fmtlib/fmt/pull/3461>`_,
|
||||
`#3487 <https://github.com/fmtlib/fmt/issues/3487>`_,
|
||||
`#3515 <https://github.com/fmtlib/fmt/pull/3515>`_).
|
||||
Thanks `@zencatalyst (Kasra Hashemi) <https://github.com/zencatalyst>`_,
|
||||
`@rlalik <https://github.com/rlalik>`_,
|
||||
`@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_.
|
||||
|
||||
* Improved build and CI configurations
|
||||
(`#3449 <https://github.com/fmtlib/fmt/issues/3449>`_,
|
||||
`#3451 <https://github.com/fmtlib/fmt/pull/3451>`_,
|
||||
`#3452 <https://github.com/fmtlib/fmt/pull/3452>`_,
|
||||
`#3453 <https://github.com/fmtlib/fmt/pull/3453>`_,
|
||||
`#3459 <https://github.com/fmtlib/fmt/pull/3459>`_,
|
||||
`#3481 <https://github.com/fmtlib/fmt/issues/3481>`_,
|
||||
`#3486 <https://github.com/fmtlib/fmt/pull/3486>`_,
|
||||
`#3489 <https://github.com/fmtlib/fmt/issues/3489>`_,
|
||||
`#3496 <https://github.com/fmtlib/fmt/pull/3496>`_,
|
||||
`#3517 <https://github.com/fmtlib/fmt/issues/3517>`_,
|
||||
`#3523 <https://github.com/fmtlib/fmt/pull/3523>`_,
|
||||
`#3563 <https://github.com/fmtlib/fmt/pull/3563>`_).
|
||||
Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_,
|
||||
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
|
||||
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||
`@petrmanek (Petr Mánek) <https://github.com/petrmanek>`_,
|
||||
`@setoye (Alta) <https://github.com/setoye>`_,
|
||||
`@abouvier (Alexandre Bouvier) <https://github.com/abouvier>`_.
|
||||
|
||||
* Fixed various warnings and compilation issues
|
||||
(`#3408 <https://github.com/fmtlib/fmt/issues/3408>`_,
|
||||
`#3424 <https://github.com/fmtlib/fmt/issues/3424>`_,
|
||||
`#3444 <https://github.com/fmtlib/fmt/issues/3444>`_,
|
||||
`#3446 <https://github.com/fmtlib/fmt/pull/3446>`_,
|
||||
`#3475 <https://github.com/fmtlib/fmt/pull/3475>`_,
|
||||
`#3482 <https://github.com/fmtlib/fmt/pull/3482>`_,
|
||||
`#3492 <https://github.com/fmtlib/fmt/issues/3492>`_,
|
||||
`#3493 <https://github.com/fmtlib/fmt/pull/3493>`_,
|
||||
`#3508 <https://github.com/fmtlib/fmt/pull/3508>`_,
|
||||
`#3509 <https://github.com/fmtlib/fmt/issues/3509>`_,
|
||||
`#3533 <https://github.com/fmtlib/fmt/issues/3533>`_,
|
||||
`#3542 <https://github.com/fmtlib/fmt/pull/3542>`_,
|
||||
`#3543 <https://github.com/fmtlib/fmt/issues/3543>`_,
|
||||
`#3540 <https://github.com/fmtlib/fmt/issues/3540>`_,
|
||||
`#3544 <https://github.com/fmtlib/fmt/pull/3544>`_,
|
||||
`#3548 <https://github.com/fmtlib/fmt/issues/3548>`_,
|
||||
`#3549 <https://github.com/fmtlib/fmt/pull/3549>`_,
|
||||
`#3550 <https://github.com/fmtlib/fmt/pull/3550>`_,
|
||||
`#3552 <https://github.com/fmtlib/fmt/pull/3552>`_).
|
||||
Thanks `@adesitter (Arnaud Desitter) <https://github.com/adesitter>`_,
|
||||
`@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_,
|
||||
`@Minty-Meeo <https://github.com/Minty-Meeo>`_,
|
||||
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||
`@TobiSchluter (Tobias Schlüter) <https://github.com/TobiSchluter>`_,
|
||||
`@kieranclancy (Kieran Clancy) <https://github.com/kieranclancy>`_,
|
||||
`@alexeedm (Dmitry Alexeev) <https://github.com/alexeedm>`_,
|
||||
`@jurihock (Jürgen Hock) <https://github.com/jurihock>`_,
|
||||
`@Ozomahtli <https://github.com/Ozomahtli>`_,
|
||||
`@razaqq <https://github.com/razaqq>`_.
|
||||
|
||||
10.0.0 - 2023-05-09
|
||||
-------------------
|
||||
|
||||
* Replaced Grisu with a new floating-point formatting algorithm for given
|
||||
precision (`#3262 <https://github.com/fmtlib/fmt/issues/3262>`_,
|
||||
`#2750 <https://github.com/fmtlib/fmt/issues/2750>`_,
|
||||
`#3269 <https://github.com/fmtlib/fmt/pull/3269>`_,
|
||||
`#3276 <https://github.com/fmtlib/fmt/pull/3276>`_).
|
||||
The new algorithm is based on Dragonbox already used for the
|
||||
shortest representation and gives substantial performance improvement:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/33922675/
|
||||
211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png
|
||||
|
||||
* Red: new algorithm
|
||||
* Green: new algorithm with ``FMT_USE_FULL_CACHE_DRAGONBOX`` defined to 1
|
||||
* Blue: old algorithm
|
||||
|
||||
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
|
||||
|
||||
* Replaced ``snprintf``-based hex float formatter with an internal
|
||||
implementation (`#3179 <https://github.com/fmtlib/fmt/pull/3179>`_,
|
||||
`#3203 <https://github.com/fmtlib/fmt/pull/3203>`_).
|
||||
This removes the last usage of ``s(n)printf`` in {fmt}.
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Fixed alignment of floating-point numbers with localization
|
||||
(`#3263 <https://github.com/fmtlib/fmt/issues/3263>`_,
|
||||
`#3272 <https://github.com/fmtlib/fmt/pull/3272>`_).
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Made handling of ``#`` consistent with ``std::format``.
|
||||
|
||||
* Improved C++20 module support
|
||||
(`#3134 <https://github.com/fmtlib/fmt/pull/3134>`_,
|
||||
`#3254 <https://github.com/fmtlib/fmt/pull/3254>`_,
|
||||
`#3386 <https://github.com/fmtlib/fmt/pull/3386>`_,
|
||||
`#3387 <https://github.com/fmtlib/fmt/pull/3387>`_,
|
||||
`#3388 <https://github.com/fmtlib/fmt/pull/3388>`_,
|
||||
`#3392 <https://github.com/fmtlib/fmt/pull/3392>`_,
|
||||
`#3397 <https://github.com/fmtlib/fmt/pull/3397>`_,
|
||||
`#3399 <https://github.com/fmtlib/fmt/pull/3399>`_,
|
||||
`#3400 <https://github.com/fmtlib/fmt/pull/3400>`_).
|
||||
Thanks `@laitingsheng (Tinson Lai) <https://github.com/laitingsheng>`_,
|
||||
`@Orvid (Orvid King) <https://github.com/Orvid>`_,
|
||||
`@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
|
||||
Switched to the `modules CMake library <https://github.com/vitaut/modules>`_
|
||||
which allows building {fmt} as a C++20 module with clang::
|
||||
|
||||
CXX=clang++ cmake -DFMT_MODULE=ON .
|
||||
make
|
||||
|
||||
* Made ``format_as`` work with any user-defined type and not just enums.
|
||||
For example (`godbolt <https://godbolt.org/z/b7rqhq5Kh>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
struct floaty_mc_floatface {
|
||||
double value;
|
||||
};
|
||||
|
||||
auto format_as(floaty_mc_floatface f) { return f.value; }
|
||||
|
||||
int main() {
|
||||
fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42"
|
||||
}
|
||||
|
||||
* Removed deprecated implicit conversions for enums and conversions to primitive
|
||||
types for compatibility with ``std::format`` and to prevent potential ODR
|
||||
violations. Use ``format_as`` instead.
|
||||
|
||||
* Added support for fill, align and width to the time point formatter
|
||||
(`#3237 <https://github.com/fmtlib/fmt/issues/3237>`_,
|
||||
`#3260 <https://github.com/fmtlib/fmt/pull/3260>`_,
|
||||
`#3275 <https://github.com/fmtlib/fmt/pull/3275>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/rKP6MGz6c>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
// prints " 2023"
|
||||
fmt::print("{:>8%Y}\n", std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Implemented formatting of subseconds
|
||||
(`#2207 <https://github.com/fmtlib/fmt/issues/2207>`_,
|
||||
`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
|
||||
`#3115 <https://github.com/fmtlib/fmt/pull/3115>`_,
|
||||
`#3143 <https://github.com/fmtlib/fmt/pull/3143>`_,
|
||||
`#3144 <https://github.com/fmtlib/fmt/pull/3144>`_,
|
||||
`#3349 <https://github.com/fmtlib/fmt/pull/3349>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/45738oGEo>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
// prints 01.234567
|
||||
fmt::print("{:%S}\n", std::chrono::microseconds(1234567));
|
||||
}
|
||||
|
||||
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_
|
||||
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||
`@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||
|
||||
* Added precision support to ``%S``
|
||||
(`#3148 <https://github.com/fmtlib/fmt/pull/3148>`_).
|
||||
Thanks `@SappyJoy (Stepan Ponomaryov) <https://github.com/SappyJoy>`_
|
||||
|
||||
* Added support for ``std::utc_time``
|
||||
(`#3098 <https://github.com/fmtlib/fmt/issues/3098>`_,
|
||||
`#3110 <https://github.com/fmtlib/fmt/pull/3110>`_).
|
||||
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_.
|
||||
|
||||
* Switched formatting of ``std::chrono::system_clock`` from local time to UTC
|
||||
for compatibility with the standard
|
||||
(`#3199 <https://github.com/fmtlib/fmt/issues/3199>`_,
|
||||
`#3230 <https://github.com/fmtlib/fmt/pull/3230>`_).
|
||||
Thanks `@ned14 (Niall Douglas) <https://github.com/ned14>`_.
|
||||
|
||||
* Added support for ``%Ez`` and ``%Oz`` to chrono formatters.
|
||||
(`#3220 <https://github.com/fmtlib/fmt/issues/3220>`_,
|
||||
`#3222 <https://github.com/fmtlib/fmt/pull/3222>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Improved validation of format specifiers for ``std::chrono::duration``
|
||||
(`#3219 <https://github.com/fmtlib/fmt/issues/3219>`_,
|
||||
`#3232 <https://github.com/fmtlib/fmt/pull/3232>`_).
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Fixed formatting of time points before the epoch
|
||||
(`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
|
||||
`#3261 <https://github.com/fmtlib/fmt/pull/3261>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
auto t = std::chrono::system_clock::from_time_t(0) -
|
||||
std::chrono::milliseconds(250);
|
||||
fmt::print("{:%S}\n", t); // prints 59.750000000
|
||||
}
|
||||
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Experimental: implemented glibc extension for padding seconds, minutes and
|
||||
hours (`#2959 <https://github.com/fmtlib/fmt/issues/2959>`_,
|
||||
`#3271 <https://github.com/fmtlib/fmt/pull/3271>`_).
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Added a formatter for ``std::exception``
|
||||
(`#2977 <https://github.com/fmtlib/fmt/issues/2977>`_,
|
||||
`#3012 <https://github.com/fmtlib/fmt/issues/3012>`_,
|
||||
`#3062 <https://github.com/fmtlib/fmt/pull/3062>`_,
|
||||
`#3076 <https://github.com/fmtlib/fmt/pull/3076>`_,
|
||||
`#3119 <https://github.com/fmtlib/fmt/pull/3119>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/8xoWGs9e4>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/std.h>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
try {
|
||||
std::vector<bool>().at(0);
|
||||
} catch(const std::exception& e) {
|
||||
fmt::print("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
prints::
|
||||
|
||||
vector<bool>::_M_range_check: __n (which is 0) >= this->size() (which is 0)
|
||||
|
||||
on libstdc++.
|
||||
Thanks `@zach2good (Zach Toogood) <https://github.com/zach2good>`_ and
|
||||
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Moved ``std::error_code`` formatter from ``fmt/os.h`` to ``fmt/std.h``.
|
||||
(`#3125 <https://github.com/fmtlib/fmt/pull/3125>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Added formatters for standard container adapters: ``std::priority_queue``,
|
||||
``std::queue`` and ``std::stack``
|
||||
(`#3215 <https://github.com/fmtlib/fmt/issues/3215>`_,
|
||||
`#3279 <https://github.com/fmtlib/fmt/pull/3279>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/74h1xY9qK>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
auto s = std::stack<bool, std::vector<bool>>();
|
||||
for (auto b: {true, false, true}) s.push(b);
|
||||
fmt::print("{}\n", s); // prints [true, false, true]
|
||||
}
|
||||
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Added a formatter for ``std::optional`` to ``fmt/std.h``.
|
||||
Thanks `@tom-huntington <https://github.com/tom-huntington>`_.
|
||||
|
||||
* Fixed formatting of valueless by exception variants
|
||||
(`#3347 <https://github.com/fmtlib/fmt/pull/3347>`_).
|
||||
Thanks `@TheOmegaCarrot <https://github.com/TheOmegaCarrot>`_.
|
||||
|
||||
* Made ``fmt::ptr`` accept ``unique_ptr`` with a custom deleter
|
||||
(`#3177 <https://github.com/fmtlib/fmt/pull/3177>`_).
|
||||
Thanks `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
|
||||
|
||||
* Fixed formatting of noncopyable ranges and nested ranges of chars
|
||||
(`#3158 <https://github.com/fmtlib/fmt/pull/3158>`_
|
||||
`#3286 <https://github.com/fmtlib/fmt/issues/3286>`_,
|
||||
`#3290 <https://github.com/fmtlib/fmt/pull/3290>`_).
|
||||
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||
|
||||
* Fixed issues with formatting of paths and ranges of paths
|
||||
(`#3319 <https://github.com/fmtlib/fmt/issues/3319>`_,
|
||||
`#3321 <https://github.com/fmtlib/fmt/pull/3321>`_
|
||||
`#3322 <https://github.com/fmtlib/fmt/issues/3322>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Improved handling of invalid Unicode in paths.
|
||||
|
||||
* Enabled compile-time checks on Apple clang 14 and later
|
||||
(`#3331 <https://github.com/fmtlib/fmt/pull/3331>`_).
|
||||
Thanks `@cloyce (Cloyce D. Spradling) <https://github.com/cloyce>`_.
|
||||
|
||||
* Improved compile-time checks of named arguments
|
||||
(`#3105 <https://github.com/fmtlib/fmt/issues/3105>`_,
|
||||
`#3214 <https://github.com/fmtlib/fmt/pull/3214>`_).
|
||||
Thanks `@rbrich (Radek Brich) <https://github.com/rbrich>`_.
|
||||
|
||||
* Fixed formatting when both alignment and ``0`` are given
|
||||
(`#3236 <https://github.com/fmtlib/fmt/issues/3236>`_,
|
||||
`#3248 <https://github.com/fmtlib/fmt/pull/3248>`_).
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Improved Unicode support in the experimental file API on Windows
|
||||
(`#3234 <https://github.com/fmtlib/fmt/issues/3234>`_,
|
||||
`#3293 <https://github.com/fmtlib/fmt/pull/3293>`_).
|
||||
Thanks `@Fros1er (Froster) <https://github.com/Fros1er>`_.
|
||||
|
||||
* Unified UTF transcoding
|
||||
(`#3416 <https://github.com/fmtlib/fmt/pull/3416>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Added support for UTF-8 digit separators via an experimental locale facet
|
||||
(`#1861 <https://github.com/fmtlib/fmt/issues/1861>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
auto loc = std::locale(
|
||||
std::locale(), new fmt::format_facet<std::locale>("’"));
|
||||
auto s = fmt::format(loc, "{:L}", 1000);
|
||||
|
||||
where ``’`` is U+2019 used as a digit separator in the de_CH locale.
|
||||
|
||||
* Added an overload of ``formatted_size`` that takes a locale
|
||||
(`#3084 <https://github.com/fmtlib/fmt/issues/3084>`_,
|
||||
`#3087 <https://github.com/fmtlib/fmt/pull/3087>`_).
|
||||
Thanks `@gerboengels <https://github.com/gerboengels>`_.
|
||||
|
||||
* Removed the deprecated ``FMT_DEPRECATED_OSTREAM``.
|
||||
|
||||
* Fixed a UB when using a null ``std::string_view`` with ``fmt::to_string``
|
||||
or format string compilation
|
||||
(`#3241 <https://github.com/fmtlib/fmt/issues/3241>`_,
|
||||
`#3244 <https://github.com/fmtlib/fmt/pull/3244>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Added ``starts_with`` to the fallback ``string_view`` implementation
|
||||
(`#3080 <https://github.com/fmtlib/fmt/pull/3080>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Added ``fmt::basic_format_string::get()`` for compatibility with
|
||||
``basic_format_string`` (`#3111 <https://github.com/fmtlib/fmt/pull/3111>`_).
|
||||
Thanks `@huangqinjin <https://github.com/huangqinjin>`_.
|
||||
|
||||
* Added ``println`` for compatibility with C++23
|
||||
(`#3267 <https://github.com/fmtlib/fmt/pull/3267>`_).
|
||||
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||
|
||||
* Renamed the ``FMT_EXPORT`` macro for shared library usage to
|
||||
``FMT_LIB_EXPORT``.
|
||||
|
||||
* Improved documentation
|
||||
(`#3108 <https://github.com/fmtlib/fmt/issues/3108>`_,
|
||||
`#3169 <https://github.com/fmtlib/fmt/issues/3169>`_,
|
||||
`#3243 <https://github.com/fmtlib/fmt/pull/3243>`_).
|
||||
`#3404 <https://github.com/fmtlib/fmt/pull/3404>`_).
|
||||
Thanks `@Cleroth <https://github.com/Cleroth>`_ and
|
||||
`@Vertexwahn <https://github.com/Vertexwahn>`_.
|
||||
|
||||
* Improved build configuration and tests
|
||||
(`#3118 <https://github.com/fmtlib/fmt/pull/3118>`_,
|
||||
`#3120 <https://github.com/fmtlib/fmt/pull/3120>`_,
|
||||
`#3188 <https://github.com/fmtlib/fmt/pull/3188>`_,
|
||||
`#3189 <https://github.com/fmtlib/fmt/issues/3189>`_,
|
||||
`#3198 <https://github.com/fmtlib/fmt/pull/3198>`_,
|
||||
`#3205 <https://github.com/fmtlib/fmt/pull/3205>`_,
|
||||
`#3207 <https://github.com/fmtlib/fmt/pull/3207>`_,
|
||||
`#3210 <https://github.com/fmtlib/fmt/pull/3210>`_,
|
||||
`#3240 <https://github.com/fmtlib/fmt/pull/3240>`_,
|
||||
`#3256 <https://github.com/fmtlib/fmt/pull/3256>`_,
|
||||
`#3264 <https://github.com/fmtlib/fmt/pull/3264>`_,
|
||||
`#3299 <https://github.com/fmtlib/fmt/issues/3299>`_,
|
||||
`#3302 <https://github.com/fmtlib/fmt/pull/3302>`_,
|
||||
`#3312 <https://github.com/fmtlib/fmt/pull/3312>`_,
|
||||
`#3317 <https://github.com/fmtlib/fmt/issues/3317>`_,
|
||||
`#3328 <https://github.com/fmtlib/fmt/pull/3328>`_,
|
||||
`#3333 <https://github.com/fmtlib/fmt/pull/3333>`_,
|
||||
`#3369 <https://github.com/fmtlib/fmt/pull/3369>`_,
|
||||
`#3373 <https://github.com/fmtlib/fmt/issues/3373>`_,
|
||||
`#3395 <https://github.com/fmtlib/fmt/pull/3395>`_,
|
||||
`#3406 <https://github.com/fmtlib/fmt/pull/3406>`_,
|
||||
`#3411 <https://github.com/fmtlib/fmt/pull/3411>`_).
|
||||
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
|
||||
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||
`@DavidKorczynski <https://github.com/DavidKorczynski>`_,
|
||||
`@ChrisThrasher (Chris Thrasher) <https://github.com/ChrisThrasher>`_,
|
||||
`@FrancoisCarouge (François Carouge) <https://github.com/FrancoisCarouge>`_,
|
||||
`@kennyweiss (Kenny Weiss) <https://github.com/kennyweiss>`_,
|
||||
`@luzpaz <https://github.com/luzpaz>`_,
|
||||
`@codeinred (Alecto Irene Perez) <https://github.com/codeinred>`_,
|
||||
`@Mixaill (Mikhail Paulyshka) <https://github.com/Mixaill>`_,
|
||||
`@joycebrum (Joyce) <https://github.com/joycebrum>`_,
|
||||
`@kevinhwang (Kevin Hwang) <https://github.com/kevinhwang>`_,
|
||||
`@Vertexwahn <https://github.com/Vertexwahn>`_.
|
||||
|
||||
* Fixed a regression in handling empty format specifiers after a colon (``{:}``)
|
||||
(`#3086 <https://github.com/fmtlib/fmt/pull/3086>`_).
|
||||
Thanks `@oxidase (Michael Krasnyk) <https://github.com/oxidase>`_.
|
||||
|
||||
* Worked around a broken implementation of ``std::is_constant_evaluated`` in
|
||||
some versions of libstdc++ on clang
|
||||
(`#3247 <https://github.com/fmtlib/fmt/issues/3247>`_,
|
||||
`#3281 <https://github.com/fmtlib/fmt/pull/3281>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Fixed formatting of volatile variables
|
||||
(`#3068 <https://github.com/fmtlib/fmt/pull/3068>`_).
|
||||
|
||||
* Fixed various warnings and compilation issues
|
||||
(`#3057 <https://github.com/fmtlib/fmt/pull/3057>`_,
|
||||
`#3066 <https://github.com/fmtlib/fmt/pull/3066>`_,
|
||||
`#3072 <https://github.com/fmtlib/fmt/pull/3072>`_,
|
||||
`#3082 <https://github.com/fmtlib/fmt/pull/3082>`_,
|
||||
`#3091 <https://github.com/fmtlib/fmt/pull/3091>`_,
|
||||
`#3092 <https://github.com/fmtlib/fmt/issues/3092>`_,
|
||||
`#3093 <https://github.com/fmtlib/fmt/pull/3093>`_,
|
||||
`#3095 <https://github.com/fmtlib/fmt/pull/3095>`_,
|
||||
`#3096 <https://github.com/fmtlib/fmt/issues/3096>`_,
|
||||
`#3097 <https://github.com/fmtlib/fmt/pull/3097>`_,
|
||||
`#3128 <https://github.com/fmtlib/fmt/issues/3128>`_,
|
||||
`#3129 <https://github.com/fmtlib/fmt/pull/3129>`_,
|
||||
`#3137 <https://github.com/fmtlib/fmt/pull/3137>`_,
|
||||
`#3139 <https://github.com/fmtlib/fmt/pull/3139>`_,
|
||||
`#3140 <https://github.com/fmtlib/fmt/issues/3140>`_,
|
||||
`#3142 <https://github.com/fmtlib/fmt/pull/3142>`_,
|
||||
`#3149 <https://github.com/fmtlib/fmt/issues/3149>`_,
|
||||
`#3150 <https://github.com/fmtlib/fmt/pull/3150>`_,
|
||||
`#3154 <https://github.com/fmtlib/fmt/issues/3154>`_,
|
||||
`#3163 <https://github.com/fmtlib/fmt/issues/3163>`_,
|
||||
`#3178 <https://github.com/fmtlib/fmt/issues/3178>`_,
|
||||
`#3184 <https://github.com/fmtlib/fmt/pull/3184>`_,
|
||||
`#3196 <https://github.com/fmtlib/fmt/pull/3196>`_,
|
||||
`#3204 <https://github.com/fmtlib/fmt/issues/3204>`_,
|
||||
`#3206 <https://github.com/fmtlib/fmt/pull/3206>`_,
|
||||
`#3208 <https://github.com/fmtlib/fmt/pull/3208>`_,
|
||||
`#3213 <https://github.com/fmtlib/fmt/issues/3213>`_,
|
||||
`#3216 <https://github.com/fmtlib/fmt/pull/3216>`_,
|
||||
`#3224 <https://github.com/fmtlib/fmt/issues/3224>`_,
|
||||
`#3226 <https://github.com/fmtlib/fmt/issues/3226>`_,
|
||||
`#3228 <https://github.com/fmtlib/fmt/issues/3228>`_,
|
||||
`#3229 <https://github.com/fmtlib/fmt/pull/3229>`_,
|
||||
`#3259 <https://github.com/fmtlib/fmt/pull/3259>`_,
|
||||
`#3274 <https://github.com/fmtlib/fmt/issues/3274>`_,
|
||||
`#3287 <https://github.com/fmtlib/fmt/issues/3287>`_,
|
||||
`#3288 <https://github.com/fmtlib/fmt/pull/3288>`_,
|
||||
`#3292 <https://github.com/fmtlib/fmt/issues/3292>`_,
|
||||
`#3295 <https://github.com/fmtlib/fmt/pull/3295>`_,
|
||||
`#3296 <https://github.com/fmtlib/fmt/pull/3296>`_,
|
||||
`#3298 <https://github.com/fmtlib/fmt/issues/3298>`_,
|
||||
`#3325 <https://github.com/fmtlib/fmt/issues/3325>`_,
|
||||
`#3326 <https://github.com/fmtlib/fmt/pull/3326>`_,
|
||||
`#3334 <https://github.com/fmtlib/fmt/issues/3334>`_,
|
||||
`#3342 <https://github.com/fmtlib/fmt/issues/3342>`_,
|
||||
`#3343 <https://github.com/fmtlib/fmt/pull/3343>`_,
|
||||
`#3351 <https://github.com/fmtlib/fmt/issues/3351>`_,
|
||||
`#3352 <https://github.com/fmtlib/fmt/pull/3352>`_,
|
||||
`#3362 <https://github.com/fmtlib/fmt/pull/3362>`_,
|
||||
`#3365 <https://github.com/fmtlib/fmt/issues/3365>`_,
|
||||
`#3366 <https://github.com/fmtlib/fmt/pull/3366>`_,
|
||||
`#3374 <https://github.com/fmtlib/fmt/pull/3374>`_,
|
||||
`#3377 <https://github.com/fmtlib/fmt/issues/3377>`_,
|
||||
`#3378 <https://github.com/fmtlib/fmt/pull/3378>`_,
|
||||
`#3381 <https://github.com/fmtlib/fmt/issues/3381>`_,
|
||||
`#3398 <https://github.com/fmtlib/fmt/pull/3398>`_,
|
||||
`#3413 <https://github.com/fmtlib/fmt/pull/3413>`_,
|
||||
`#3415 <https://github.com/fmtlib/fmt/issues/3415>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
|
||||
`@NewbieOrange <https://github.com/NewbieOrange>`_,
|
||||
`@EngineLessCC (VivyaCC) <https://github.com/EngineLessCC>`_,
|
||||
`@asmaloney (Andy Maloney) <https://github.com/asmaloney>`_,
|
||||
`@HazardyKnusperkeks (Björn Schäpers)
|
||||
<https://github.com/HazardyKnusperkeks>`_,
|
||||
`@sergiud (Sergiu Deitsch) <https://github.com/sergiud>`_,
|
||||
`@Youw (Ihor Dutchak) <https://github.com/Youw>`_,
|
||||
`@thesmurph <https://github.com/thesmurph>`_,
|
||||
`@czudziakm (Maksymilian Czudziak) <https://github.com/czudziakm>`_,
|
||||
`@Roman-Koshelev <https://github.com/Roman-Koshelev>`_,
|
||||
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||
`@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_,
|
||||
`@russelltg (Russell Greene) <https://github.com/russelltg>`_,
|
||||
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
|
||||
`@tmartin-gh <https://github.com/tmartin-gh>`_,
|
||||
`@Zhaojun-Liu (June Liu) <https://github.com/Zhaojun-Liu>`_,
|
||||
`@louiswins (Louis Wilson) <https://github.com/louiswins>`_,
|
||||
`@mogemimi <https://github.com/mogemimi>`_.
|
||||
|
||||
9.1.0 - 2022-08-27
|
||||
------------------
|
||||
|
||||
@ -105,6 +771,7 @@
|
||||
`#2982 <https://github.com/fmtlib/fmt/pull/2982>`_,
|
||||
`#2985 <https://github.com/fmtlib/fmt/pull/2985>`_,
|
||||
`#2988 <https://github.com/fmtlib/fmt/issues/2988>`_,
|
||||
`#2989 <https://github.com/fmtlib/fmt/issues/2989>`_,
|
||||
`#3000 <https://github.com/fmtlib/fmt/issues/3000>`_,
|
||||
`#3006 <https://github.com/fmtlib/fmt/issues/3006>`_,
|
||||
`#3014 <https://github.com/fmtlib/fmt/issues/3014>`_,
|
||||
@ -2260,7 +2927,7 @@
|
||||
<https://github.com/kwesolowski>`_.
|
||||
|
||||
* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
|
||||
macro to prevent interferring with fuzzing of projects using {fmt}
|
||||
macro to prevent interfering with fuzzing of projects using {fmt}
|
||||
(`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
|
||||
Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
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
|
||||
|
70
README.rst
70
README.rst
@ -22,6 +22,9 @@
|
||||
:alt: Ask questions at StackOverflow with the tag fmt
|
||||
:target: https://stackoverflow.com/questions/tagged/fmt
|
||||
|
||||
.. image:: https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge
|
||||
:target: https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt
|
||||
|
||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
@ -47,7 +50,9 @@ Features
|
||||
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
||||
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
|
||||
round-trip guarantees
|
||||
round-trip guarantees using the `Dragonbox <https://github.com/jk-jeon/dragonbox>`_
|
||||
algorithm
|
||||
* Portable Unicode support
|
||||
* Safe `printf implementation
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
||||
extension for positional arguments
|
||||
@ -64,7 +69,7 @@ Features
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
|
||||
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
|
||||
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
* Safety: the library is fully type-safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors
|
||||
* Ease of use: small self-contained code base, no external dependencies,
|
||||
@ -74,7 +79,7 @@ Features
|
||||
consistent output across platforms and support for older compilers
|
||||
* Clean warning-free codebase even on high warning levels such as
|
||||
``-Wall -Wextra -pedantic``
|
||||
* Locale-independence by default
|
||||
* Locale independence by default
|
||||
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
|
||||
|
||||
See the `documentation <https://fmt.dev>`_ for more details.
|
||||
@ -191,24 +196,24 @@ Speed tests
|
||||
================= ============= ===========
|
||||
Library Method Run Time, s
|
||||
================= ============= ===========
|
||||
libc printf 1.04
|
||||
libc++ std::ostream 3.05
|
||||
{fmt} 6.1.1 fmt::print 0.75
|
||||
Boost Format 1.67 boost::format 7.24
|
||||
Folly Format folly::format 2.23
|
||||
libc printf 0.91
|
||||
libc++ std::ostream 2.49
|
||||
{fmt} 9.1 fmt::print 0.74
|
||||
Boost Format 1.80 boost::format 6.26
|
||||
Folly Format folly::format 1.87
|
||||
================= ============= ===========
|
||||
|
||||
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
||||
{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``.
|
||||
|
||||
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
||||
12.6.1 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
||||
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
||||
further details refer to the `source
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
|
||||
|
||||
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
|
||||
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||
IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
||||
`ryu <https://github.com/ulfjack/ryu>`_:
|
||||
|
||||
@ -224,7 +229,7 @@ The script `bloat-test.py
|
||||
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
||||
tests compile time and code bloat for nontrivial projects.
|
||||
It generates 100 translation units and uses ``printf()`` or its alternative
|
||||
five times in each to simulate a medium sized project. The resulting
|
||||
five times in each to simulate a medium-sized project. The resulting
|
||||
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
||||
macOS Sierra, best of three) is shown in the following tables.
|
||||
|
||||
@ -245,7 +250,7 @@ As you can see, {fmt} has 60% less overhead in terms of resulting binary code
|
||||
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
||||
and Folly Format have the largest overheads.
|
||||
|
||||
``printf+string`` is the same as ``printf`` but with extra ``<string>``
|
||||
``printf+string`` is the same as ``printf`` but with an extra ``<string>``
|
||||
include to measure the overhead of the latter.
|
||||
|
||||
**Non-optimized build**
|
||||
@ -261,14 +266,14 @@ Boost Format 54.1 365 303
|
||||
Folly Format 79.9 445 430
|
||||
============= =============== ==================== ==================
|
||||
|
||||
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
||||
``libc``, ``lib(std)c++``, and ``libfmt`` are all linked as shared libraries to
|
||||
compare formatting function overhead only. Boost Format is a
|
||||
header-only library so it doesn't provide any linkage options.
|
||||
|
||||
Running the tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please refer to `Building the library`__ for the instructions on how to build
|
||||
Please refer to `Building the library`__ for instructions on how to build
|
||||
the library and run the unit tests.
|
||||
|
||||
__ https://fmt.dev/latest/usage.html#building-the-library
|
||||
@ -293,9 +298,12 @@ or the bloat test::
|
||||
Migrating code
|
||||
--------------
|
||||
|
||||
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
|
||||
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
|
||||
``fmt::print``.
|
||||
`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`_ v17 (not yet
|
||||
released) provides the `modernize-use-std-print
|
||||
<https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html>`_
|
||||
check that is capable of converting occurrences of ``printf`` and
|
||||
``fprintf`` to ``fmt::print`` if configured to do so. (By default it
|
||||
converts to ``std::print``.)
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
@ -303,8 +311,6 @@ Projects using this library
|
||||
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
|
||||
real-time strategy game
|
||||
|
||||
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
an open-source library for mathematical programming
|
||||
|
||||
@ -322,8 +328,10 @@ Projects using this library
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: a compiler cache
|
||||
|
||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
|
||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: an analytical database
|
||||
management system
|
||||
|
||||
* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator
|
||||
|
||||
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
@ -360,6 +368,10 @@ Projects using this library
|
||||
|
||||
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
||||
|
||||
* `libunicode <https://github.com/contour-terminal/libunicode/>`_: a modern C++17 Unicode library
|
||||
|
||||
* `MariaDB <https://mariadb.org/>`_: relational database management system
|
||||
|
||||
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
||||
research programming language for concurrent ownership
|
||||
|
||||
@ -389,7 +401,7 @@ Projects using this library
|
||||
proxy
|
||||
|
||||
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
|
||||
for mission critical systems written in C++
|
||||
for mission-critical systems written in C++
|
||||
|
||||
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
|
||||
library
|
||||
@ -413,6 +425,9 @@ Projects using this library
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
||||
MMORPG framework
|
||||
|
||||
* `🐙 userver framework <https://userver.tech/>`_: open-source asynchronous
|
||||
framework with a rich set of abstractions and database drivers
|
||||
|
||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
||||
terminal
|
||||
|
||||
@ -470,7 +485,7 @@ error handling is awkward.
|
||||
Boost Format
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This is a very powerful library which supports both ``printf``-like format
|
||||
This is a very powerful library that supports both ``printf``-like format
|
||||
strings and positional arguments. Its main drawback is performance. According to
|
||||
various benchmarks, it is much slower than other methods considered here. Boost
|
||||
Format also has excessive build times and severe code bloat issues (see
|
||||
@ -479,7 +494,7 @@ Format also has excessive build times and severe code bloat issues (see
|
||||
FastFormat
|
||||
~~~~~~~~~~
|
||||
|
||||
This is an interesting library which is fast, safe and has positional arguments.
|
||||
This is an interesting library that is fast, safe, and has positional arguments.
|
||||
However, it has significant limitations, citing its author:
|
||||
|
||||
Three features that have no hope of being accommodated within the
|
||||
@ -495,7 +510,7 @@ restrictive for using it in some projects.
|
||||
Boost Spirit.Karma
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is not really a formatting library but I decided to include it here for
|
||||
This is not a formatting library but I decided to include it here for
|
||||
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
||||
with arguments. The library is pretty fast, but slower on integer formatting
|
||||
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
|
||||
@ -514,7 +529,7 @@ Documentation License
|
||||
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
|
||||
section in the documentation is based on the one from Python `string module
|
||||
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
|
||||
For this reason the documentation is distributed under the Python Software
|
||||
For this reason, the documentation is distributed under the Python Software
|
||||
Foundation license available in `doc/python-license.txt
|
||||
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||
It only applies if you distribute the documentation of {fmt}.
|
||||
@ -523,8 +538,7 @@ Maintainers
|
||||
-----------
|
||||
|
||||
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
||||
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
|
||||
<https://github.com/foonathan>`_) with contributions from many other people.
|
||||
<https://github.com/vitaut>`_) with contributions from many other people.
|
||||
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
||||
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
|
||||
Let us know if your contribution is not listed or mentioned incorrectly and
|
||||
|
345
doc/api.rst
345
doc/api.rst
@ -17,6 +17,7 @@ The {fmt} library API consists of the following parts:
|
||||
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
||||
* :ref:`fmt/os.h <os-api>`: system APIs
|
||||
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||
* :ref:`fmt/args.h <args-api>`: dynamic argument lists
|
||||
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
|
||||
|
||||
@ -31,7 +32,8 @@ Core API
|
||||
``fmt/core.h`` defines the core API which provides main formatting functions
|
||||
for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
|
||||
dependencies for better compile times. This header is only beneficial when
|
||||
using {fmt} as a library and not in the header-only mode.
|
||||
using {fmt} as a library (the default) and not in the header-only mode.
|
||||
It also provides ``formatter`` specializations for built-in and string types.
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>`
|
||||
similar to that of Python's `str.format
|
||||
@ -70,112 +72,18 @@ checked at compile time in C++20. To pass a runtime format string wrap it in
|
||||
Compile-Time Format String Checks
|
||||
---------------------------------
|
||||
|
||||
Compile-time checks are enabled by default on compilers that support C++20
|
||||
``consteval``. On older compilers you can use the ``FMT_STRING`` macro defined
|
||||
in ``fmt/format.h`` instead. It requires C++14 and is a no-op in C++11.
|
||||
Compile-time format string checks are enabled by default on compilers
|
||||
that support C++20 ``consteval``. On older compilers you can use the
|
||||
:ref:`FMT_STRING <legacy-checks>`: macro defined in ``fmt/format.h`` instead.
|
||||
|
||||
.. doxygendefine:: FMT_STRING
|
||||
|
||||
To force the use of legacy compile-time checks, define the preprocessor variable
|
||||
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
||||
will fail to compile with regular strings. Runtime-checked formatting is still
|
||||
possible using ``fmt::vformat``, ``fmt::vprint``, etc.
|
||||
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_string
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_string
|
||||
|
||||
.. doxygenfunction:: fmt::runtime(string_view) -> basic_runtime<char>
|
||||
|
||||
Named Arguments
|
||||
---------------
|
||||
|
||||
.. doxygenfunction:: fmt::arg(const S&, const T&)
|
||||
|
||||
Named arguments are not supported in compile-time checks at the moment.
|
||||
|
||||
Argument Lists
|
||||
--------------
|
||||
|
||||
You can create your own formatting function with compile-time checks and small
|
||||
binary footprint, for example (https://godbolt.org/z/oba4Mc):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
void vlog(const char* file, int line, fmt::string_view format,
|
||||
fmt::format_args args) {
|
||||
fmt::print("{}: {}: ", file, line);
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void log(const char* file, int line, const S& format, Args&&... args) {
|
||||
vlog(file, line, format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
#define MY_LOG(format, ...) \
|
||||
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
|
||||
|
||||
MY_LOG("invalid squishiness: {}", 42);
|
||||
|
||||
Note that ``vlog`` is not parameterized on argument types which improves compile
|
||||
times and reduces binary code size compared to a fully parameterized version.
|
||||
|
||||
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||
|
||||
.. doxygenclass:: fmt::format_arg_store
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::dynamic_format_arg_store
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_args
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_args
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_arg
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_parse_context
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_context
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_context
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
.. doxygenclass:: fmt::basic_string_view
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::string_view
|
||||
|
||||
Locale
|
||||
------
|
||||
|
||||
All formatting is locale-independent by default. Use the ``'L'`` format
|
||||
specifier to insert the appropriate number separator characters from the
|
||||
locale::
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <locale>
|
||||
|
||||
std::locale::global(std::locale("en_US.UTF-8"));
|
||||
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
|
||||
|
||||
.. _format-api:
|
||||
|
||||
Format API
|
||||
==========
|
||||
|
||||
``fmt/format.h`` defines the full format API providing additional formatting
|
||||
functions and locale support.
|
||||
.. doxygenfunction:: fmt::runtime(string_view) -> runtime_format_string<>
|
||||
|
||||
.. _udt:
|
||||
|
||||
@ -185,14 +93,39 @@ Formatting User-Defined Types
|
||||
The {fmt} library provides formatters for many standard C++ types.
|
||||
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
|
||||
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
|
||||
and time formatting and :ref:`fmt/std.h <std-api>` for path and variant
|
||||
formatting.
|
||||
and time formatting and :ref:`fmt/std.h <std-api>` for other standard library
|
||||
types.
|
||||
|
||||
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
|
||||
template and implement ``parse`` and ``format`` methods::
|
||||
There are two ways to make a user-defined type formattable: providing a
|
||||
``format_as`` function or specializing the ``formatter`` struct template.
|
||||
|
||||
Use ``format_as`` if you want to make your type formattable as some other type
|
||||
with the same format specifiers. The ``format_as`` function should take an
|
||||
object of your type and return an object of a formattable type. It should be
|
||||
defined in the same namespace as your type.
|
||||
|
||||
Example (https://godbolt.org/z/r7vvGE1v7)::
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace kevin_namespacy {
|
||||
enum class film {
|
||||
house_of_cards, american_beauty, se7en = 7
|
||||
};
|
||||
auto format_as(film f) { return fmt::underlying(f); }
|
||||
}
|
||||
|
||||
int main() {
|
||||
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
|
||||
}
|
||||
|
||||
Using the specialization API is more complex but gives you full control over
|
||||
parsing and formatting. To use this method specialize the ``formatter`` struct
|
||||
template for your type and implement ``parse`` and ``format`` methods.
|
||||
For example::
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
struct point {
|
||||
double x, y;
|
||||
};
|
||||
@ -202,7 +135,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||
char presentation = 'f';
|
||||
|
||||
// Parses format specifications of the form ['f' | 'e'].
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator {
|
||||
// [ctx.begin(), ctx.end()) is a character range that contains a part of
|
||||
// the format string starting from the format specifications to be parsed,
|
||||
// e.g. in
|
||||
@ -223,7 +156,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
|
||||
|
||||
// Check if reached the end of the range:
|
||||
if (it != end && *it != '}') throw format_error("invalid format");
|
||||
if (it != end && *it != '}') throw_format_error("invalid format");
|
||||
|
||||
// Return an iterator past the end of the parsed range:
|
||||
return it;
|
||||
@ -231,8 +164,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||
|
||||
// Formats the point p using the parsed format specification (presentation)
|
||||
// stored in this formatter.
|
||||
template <typename FormatContext>
|
||||
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
auto format(const point& p, format_context& ctx) const -> format_context::iterator {
|
||||
// ctx.out() is an output iterator to write to.
|
||||
return presentation == 'f'
|
||||
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
|
||||
@ -249,22 +181,33 @@ Then you can pass objects of type ``point`` to any formatting function::
|
||||
You can also reuse existing formatters via inheritance or composition, for
|
||||
example::
|
||||
|
||||
// color.h:
|
||||
#include <fmt/core.h>
|
||||
|
||||
enum class color {red, green, blue};
|
||||
|
||||
template <> struct fmt::formatter<color>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(color c, FormatContext& ctx) const {
|
||||
string_view name = "unknown";
|
||||
switch (c) {
|
||||
case color::red: name = "red"; break;
|
||||
case color::green: name = "green"; break;
|
||||
case color::blue: name = "blue"; break;
|
||||
}
|
||||
return formatter<string_view>::format(name, ctx);
|
||||
}
|
||||
|
||||
auto format(color c, format_context& ctx) const;
|
||||
};
|
||||
|
||||
// color.cc:
|
||||
#include "color.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
auto fmt::formatter<color>::format(color c, format_context& ctx) const {
|
||||
string_view name = "unknown";
|
||||
switch (c) {
|
||||
case color::red: name = "red"; break;
|
||||
case color::green: name = "green"; break;
|
||||
case color::blue: name = "blue"; break;
|
||||
}
|
||||
return formatter<string_view>::format(name, ctx);
|
||||
}
|
||||
|
||||
Note that ``formatter<string_view>::format`` is defined in ``fmt/format.h`` so
|
||||
it has to be included in the source file.
|
||||
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
|
||||
all string format specifications, for example
|
||||
|
||||
@ -276,8 +219,9 @@ will return ``" blue"``.
|
||||
|
||||
You can also write a formatter for a hierarchy of classes::
|
||||
|
||||
// demo.h:
|
||||
#include <type_traits>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
struct A {
|
||||
virtual ~A() {}
|
||||
@ -291,41 +235,106 @@ You can also write a formatter for a hierarchy of classes::
|
||||
template <typename T>
|
||||
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
|
||||
fmt::formatter<std::string> {
|
||||
template <typename FormatCtx>
|
||||
auto format(const A& a, FormatCtx& ctx) const {
|
||||
auto format(const A& a, format_context& ctx) const {
|
||||
return fmt::formatter<std::string>::format(a.name(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// demo.cc:
|
||||
#include "demo.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
int main() {
|
||||
B b;
|
||||
A& a = b;
|
||||
fmt::print("{}", a); // prints "B"
|
||||
}
|
||||
|
||||
If a type provides both a ``formatter`` specialization and an implicit
|
||||
conversion to a formattable type, the specialization takes precedence over the
|
||||
conversion.
|
||||
Providing both a ``formatter`` specialization and a ``format_as`` overload is
|
||||
disallowed.
|
||||
|
||||
For enums {fmt} also provides the ``format_as`` extension API. To format an enum
|
||||
via this API define ``format_as`` that takes this enum and converts it to the
|
||||
underlying type. ``format_as`` should be defined in the same namespace as the
|
||||
enum.
|
||||
Named Arguments
|
||||
---------------
|
||||
|
||||
Example (https://godbolt.org/z/r7vvGE1v7)::
|
||||
.. doxygenfunction:: fmt::arg(const S&, const T&)
|
||||
|
||||
#include <fmt/format.h>
|
||||
Named arguments are not supported in compile-time checks at the moment.
|
||||
|
||||
namespace kevin_namespacy {
|
||||
enum class film {
|
||||
house_of_cards, american_beauty, se7en = 7
|
||||
};
|
||||
auto format_as(film f) { return fmt::underlying(f); }
|
||||
}
|
||||
Argument Lists
|
||||
--------------
|
||||
|
||||
int main() {
|
||||
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
|
||||
}
|
||||
You can create your own formatting function with compile-time checks and small
|
||||
binary footprint, for example (https://godbolt.org/z/vajfWEG4b):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
void vlog(const char* file, int line, fmt::string_view format,
|
||||
fmt::format_args args) {
|
||||
fmt::print("{}: {}: ", file, line);
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void log(const char* file, int line, fmt::format_string<T...> format, T&&... args) {
|
||||
vlog(file, line, format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
#define MY_LOG(format, ...) log(__FILE__, __LINE__, format, __VA_ARGS__)
|
||||
|
||||
MY_LOG("invalid squishiness: {}", 42);
|
||||
|
||||
Note that ``vlog`` is not parameterized on argument types which improves compile
|
||||
times and reduces binary code size compared to a fully parameterized version.
|
||||
|
||||
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||
|
||||
.. doxygenclass:: fmt::format_arg_store
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_args
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_args
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_arg
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_parse_context
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_context
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_context
|
||||
|
||||
.. _args-api:
|
||||
|
||||
Dynamic Argument Lists
|
||||
----------------------
|
||||
|
||||
The header ``fmt/args.h`` provides ``dynamic_format_arg_store``, a builder-like
|
||||
API that can be used to construct format argument lists dynamically.
|
||||
|
||||
.. doxygenclass:: fmt::dynamic_format_arg_store
|
||||
:members:
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
.. doxygenclass:: fmt::basic_string_view
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::string_view
|
||||
|
||||
.. _format-api:
|
||||
|
||||
Format API
|
||||
==========
|
||||
|
||||
``fmt/format.h`` defines the full format API providing additional formatting
|
||||
functions and locale support.
|
||||
|
||||
Literal-Based API
|
||||
-----------------
|
||||
@ -338,7 +347,7 @@ Utilities
|
||||
---------
|
||||
|
||||
.. doxygenfunction:: fmt::ptr(T p) -> const void*
|
||||
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
|
||||
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T, Deleter> &p) -> const void*
|
||||
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
|
||||
|
||||
.. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type
|
||||
@ -404,6 +413,41 @@ normally don't do any allocations for built-in and string types except for
|
||||
non-default floating-point formatting that occasionally falls back on
|
||||
``sprintf``.
|
||||
|
||||
Locale
|
||||
------
|
||||
|
||||
All formatting is locale-independent by default. Use the ``'L'`` format
|
||||
specifier to insert the appropriate number separator characters from the
|
||||
locale::
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <locale>
|
||||
|
||||
std::locale::global(std::locale("en_US.UTF-8"));
|
||||
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
|
||||
|
||||
``fmt/format.h`` provides the following overloads of formatting functions that
|
||||
take ``std::locale`` as a parameter. The locale type is a template parameter to
|
||||
avoid the expensive ``<locale>`` include.
|
||||
|
||||
.. doxygenfunction:: format(const Locale& loc, format_string<T...> fmt, T&&... args) -> std::string
|
||||
.. doxygenfunction:: format_to(OutputIt out, const Locale& loc, format_string<T...> fmt, T&&... args) -> OutputIt
|
||||
.. doxygenfunction:: formatted_size(const Locale& loc, format_string<T...> fmt, T&&... args) -> size_t
|
||||
|
||||
.. _legacy-checks:
|
||||
|
||||
Legacy Compile-Time Format String Checks
|
||||
----------------------------------------
|
||||
|
||||
``FMT_STRING`` enables compile-time checks on older compilers. It requires C++14
|
||||
or later and is a no-op in C++11.
|
||||
|
||||
.. doxygendefine:: FMT_STRING
|
||||
|
||||
To force the use of legacy compile-time checks, define the preprocessor variable
|
||||
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
||||
will fail to compile with regular strings.
|
||||
|
||||
.. _ranges-api:
|
||||
|
||||
Range and Tuple Formatting
|
||||
@ -477,6 +521,7 @@ Standard Library Types Formatting
|
||||
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
|
||||
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
|
||||
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
|
||||
* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_
|
||||
|
||||
Formatting Variants
|
||||
-------------------
|
||||
@ -504,10 +549,20 @@ Format String Compilation
|
||||
``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings
|
||||
marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into
|
||||
efficient formatting code at compile-time. This supports arguments of built-in
|
||||
and string types as well as user-defined types with ``constexpr`` ``parse``
|
||||
functions in their ``formatter`` specializations. Format string compilation can
|
||||
generate more binary code compared to the default API and is only recommended in
|
||||
places where formatting is a performance bottleneck.
|
||||
and string types as well as user-defined types with ``format`` functions taking
|
||||
the format context type as a template parameter in their ``formatter``
|
||||
specializations. For example::
|
||||
|
||||
template <> struct fmt::formatter<point> {
|
||||
constexpr auto parse(format_parse_context& ctx);
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const point& p, FormatContext& ctx) const;
|
||||
};
|
||||
|
||||
Format string compilation can generate more binary code compared to the default
|
||||
API and is only recommended in places where formatting is a performance
|
||||
bottleneck.
|
||||
|
||||
.. doxygendefine:: FMT_COMPILE
|
||||
|
||||
@ -580,7 +635,7 @@ the POSIX extension for positional arguments. Unlike their standard
|
||||
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
|
||||
argument type doesn't match its format specification.
|
||||
|
||||
.. doxygenfunction:: printf(const S &format_str, const T&... args)
|
||||
.. doxygenfunction:: printf(string_view fmt, const T&... args) -> int
|
||||
|
||||
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
|
||||
|
||||
|
11
doc/build.py
11
doc/build.py
@ -4,7 +4,7 @@
|
||||
import errno, os, re, sys
|
||||
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
|
||||
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0']
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0', '10.1.0', '10.1.1']
|
||||
|
||||
class Pip:
|
||||
def __init__(self, venv_dir):
|
||||
@ -49,9 +49,9 @@ def build_docs(version='dev', **kwargs):
|
||||
GENERATE_MAN = NO
|
||||
GENERATE_RTF = NO
|
||||
CASE_SENSE_NAMES = NO
|
||||
INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
|
||||
{0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \
|
||||
{0}/xchar.h
|
||||
INPUT = {0}/args.h {0}/chrono.h {0}/color.h {0}/core.h \
|
||||
{0}/compile.h {0}/format.h {0}/os.h {0}/ostream.h \
|
||||
{0}/printf.h {0}/xchar.h
|
||||
QUIET = YES
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
AUTOLINK_SUPPORT = NO
|
||||
@ -83,8 +83,7 @@ def build_docs(version='dev', **kwargs):
|
||||
internal_symbols = [
|
||||
'fmt::detail::.*',
|
||||
'basic_data<>',
|
||||
'fmt::type_identity',
|
||||
'fmt::dynamic_formatter'
|
||||
'fmt::type_identity'
|
||||
]
|
||||
noisy_warnings = [
|
||||
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
|
||||
|
142
doc/syntax.rst
142
doc/syntax.rst
@ -109,8 +109,8 @@ Note that unless a minimum field width is defined, the field width will always
|
||||
be the same size as the data to fill it, so that the alignment option has no
|
||||
meaning in this case.
|
||||
|
||||
The *sign* option is only valid for number types, and can be one of the
|
||||
following:
|
||||
The *sign* option is only valid for floating point and signed integer types,
|
||||
and can be one of the following:
|
||||
|
||||
+---------+------------------------------------------------------------+
|
||||
| Option | Meaning |
|
||||
@ -304,8 +304,8 @@ The available presentation types for pointers are:
|
||||
Chrono Format Specifications
|
||||
============================
|
||||
|
||||
Format specifications for chrono types and ``std::tm`` have the following
|
||||
syntax:
|
||||
Format specifications for chrono duration and time point types as well as
|
||||
``std::tm`` have the following syntax:
|
||||
|
||||
.. productionlist:: sf
|
||||
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
|
||||
@ -321,20 +321,89 @@ syntax:
|
||||
Literal chars are copied unchanged to the output. Precision is valid only for
|
||||
``std::chrono::duration`` types with a floating-point representation type.
|
||||
|
||||
The available presentation types (*chrono_type*) for chrono durations and time
|
||||
points are:
|
||||
The available presentation types (*chrono_type*) are:
|
||||
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| Type | Meaning |
|
||||
+=========+====================================================================+
|
||||
| ``'a'`` | The abbreviated weekday name, e.g. "Sat". If the value does not |
|
||||
| | contain a valid weekday, an exception of type ``format_error`` is |
|
||||
| | thrown. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'A'`` | The full weekday name, e.g. "Saturday". If the value does not |
|
||||
| | contain a valid weekday, an exception of type ``format_error`` is |
|
||||
| | thrown. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'b'`` | The abbreviated month name, e.g. "Nov". If the value does not |
|
||||
| | contain a valid month, an exception of type ``format_error`` is |
|
||||
| | thrown. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'B'`` | The full month name, e.g. "November". If the value does not |
|
||||
| | contain a valid month, an exception of type ``format_error`` is |
|
||||
| | thrown. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'c'`` | The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". |
|
||||
| | The modified command ``%Ec`` produces the locale's alternate date |
|
||||
| | and time representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'C'`` | The year divided by 100 using floored division, e.g. "55". If the |
|
||||
| | result is a single decimal digit, it is prefixed with 0. |
|
||||
| | The modified command ``%EC`` produces the locale's alternative |
|
||||
| | representation of the century. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'d'`` | The day of month as a decimal number. If the result is a single |
|
||||
| | decimal digit, it is prefixed with 0. The modified command ``%Od`` |
|
||||
| | produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'D'`` | Equivalent to ``%m/%d/%y``, e.g. "11/12/55". |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'e'`` | The day of month as a decimal number. If the result is a single |
|
||||
| | decimal digit, it is prefixed with a space. The modified command |
|
||||
| | ``%Oe`` produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'F'`` | Equivalent to ``%Y-%m-%d``, e.g. "1955-11-12". |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'g'`` | The last two decimal digits of the ISO week-based year. If the |
|
||||
| | result is a single digit it is prefixed by 0. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'G'`` | The ISO week-based year as a decimal number. If the result is less |
|
||||
| | than four digits it is left-padded with 0 to four digits. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'h'`` | Equivalent to ``%b``, e.g. "Nov". |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a |
|
||||
| | single digit, it is prefixed with 0. The modified command ``%OH`` |
|
||||
| | produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'I'`` | The hour (12-hour clock) as a decimal number. If the result is a |
|
||||
| | single digit, it is prefixed with 0. The modified command ``%OI`` |
|
||||
| | produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'j'`` | If the type being formatted is a specialization of duration, the |
|
||||
| | decimal number of days without padding. Otherwise, the day of the |
|
||||
| | year as a decimal number. Jan 1 is 001. If the result is less than |
|
||||
| | three digits, it is left-padded with 0 to three digits. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'m'`` | The month as a decimal number. Jan is 01. If the result is a |
|
||||
| | single digit, it is prefixed with 0. The modified command ``%Om`` |
|
||||
| | produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'M'`` | The minute as a decimal number. If the result is a single digit, |
|
||||
| | it is prefixed with 0. The modified command ``%OM`` produces the |
|
||||
| | locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'n'`` | A new-line character. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'p'`` | The AM/PM designations associated with a 12-hour clock. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'q'`` | The duration's unit suffix. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'Q'`` | The duration's numeric value (as if extracted via ``.count()``). |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'r'`` | The 12-hour clock time, e.g. "10:04:00 PM". |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'R'`` | Equivalent to ``%H:%M``, e.g. "22:04". |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
|
||||
| | 10, the result is prefixed with 0. If the precision of the input |
|
||||
| | cannot be exactly represented with seconds, then the format is a |
|
||||
@ -345,9 +414,66 @@ points are:
|
||||
| | decimal point is localized according to the locale. The modified |
|
||||
| | command ``%OS`` produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'t'`` | A horizontal-tab character. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'T'`` | Equivalent to ``%H:%M:%S``. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'u'`` | The ISO weekday as a decimal number (1-7), where Monday is 1. The |
|
||||
| | modified command ``%Ou`` produces the locale's alternative |
|
||||
| | representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'U'`` | The week number of the year as a decimal number. The first Sunday |
|
||||
| | of the year is the first day of week 01. Days of the same year |
|
||||
| | prior to that are in week 00. If the result is a single digit, it |
|
||||
| | is prefixed with 0. The modified command ``%OU`` produces the |
|
||||
| | locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'V'`` | The ISO week-based week number as a decimal number. If the result |
|
||||
| | is a single digit, it is prefixed with 0. The modified command |
|
||||
| | ``%OV`` produces the locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'w'`` | The weekday as a decimal number (0-6), where Sunday is 0. |
|
||||
| | The modified command ``%Ow`` produces the locale's alternative |
|
||||
| | representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'W'`` | The week number of the year as a decimal number. The first Monday |
|
||||
| | of the year is the first day of week 01. Days of the same year |
|
||||
| | prior to that are in week 00. If the result is a single digit, it |
|
||||
| | is prefixed with 0. The modified command ``%OW`` produces the |
|
||||
| | locale's alternative representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'x'`` | The date representation, e.g. "11/12/55". The modified command |
|
||||
| | ``%Ex`` produces the locale's alternate date representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'X'`` | The time representation, e.g. "10:04:00". The modified command |
|
||||
| | ``%EX`` produces the locale's alternate time representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'y'`` | The last two decimal digits of the year. If the result is a single |
|
||||
| | digit it is prefixed by 0. The modified command ``%Oy`` produces |
|
||||
| | the locale's alternative representation. The modified command |
|
||||
| | ``%Ey`` produces the locale's alternative representation of offset |
|
||||
| | from ``%EC`` (year only). |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'Y'`` | The year as a decimal number. If the result is less than four |
|
||||
| | digits it is left-padded with 0 to four digits. The modified |
|
||||
| | command ``%EY`` produces the locale's alternative full year |
|
||||
| | representation. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'z'`` | The offset from UTC in the ISO 8601:2004 format. For example -0430 |
|
||||
| | refers to 4 hours 30 minutes behind UTC. If the offset is zero, |
|
||||
| | +0000 is used. The modified commands ``%Ez`` and ``%Oz`` insert a |
|
||||
| | ``:`` between the hours and minutes: -04:30. If the offset |
|
||||
| | information is not available, an exception of type |
|
||||
| | ``format_error`` is thrown. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'Z'`` | The time zone abbreviation. If the time zone abbreviation is not |
|
||||
| | available, an exception of type ``format_error`` is thrown. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
| ``'%'`` | A % character. |
|
||||
+---------+--------------------------------------------------------------------+
|
||||
|
||||
Specifiers that have a calendaric component such as ``'d'`` (the day of month)
|
||||
are valid only for ``std::tm`` and not durations or time points.
|
||||
are valid only for ``std::tm`` and time points but not durations.
|
||||
|
||||
.. range-specs:
|
||||
|
||||
@ -371,7 +497,7 @@ Examples::
|
||||
fmt::format("{}", std::vector{10, 20, 30});
|
||||
// Result: [10, 20, 30]
|
||||
fmt::format("{::#x}", std::vector{10, 20, 30});
|
||||
// Result: [0xa, 0x14, 0x13]
|
||||
// Result: [0xa, 0x14, 0x1e]
|
||||
fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'});
|
||||
// Result: ['h', 'e', 'l', 'l', 'o']
|
||||
fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'});
|
||||
|
4
fmt_src.pri
Normal file
4
fmt_src.pri
Normal file
@ -0,0 +1,4 @@
|
||||
HEADERS +=
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/src/format.cc
|
@ -1,4 +1,4 @@
|
||||
// Formatting library for C++ - dynamic format arguments
|
||||
// Formatting library for C++ - dynamic argument lists
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
@ -203,7 +203,7 @@ struct rgb {
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
@ -225,8 +225,7 @@ struct color_type {
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
/** A text style consisting of foreground and background colors and emphasis. */
|
||||
class text_style {
|
||||
@ -323,7 +322,7 @@ FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
@ -423,26 +422,6 @@ FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char> inline void fputs(const Char* chars, FILE* stream) {
|
||||
int result = std::fputs(chars, stream);
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
template <> inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) {
|
||||
int result = std::fputws(chars, stream);
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) {
|
||||
fputs("\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) {
|
||||
fputs(L"\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
buffer.append(reset_color.begin(), reset_color.end());
|
||||
@ -477,19 +456,21 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, detail::to_string_view(format), args);
|
||||
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
|
||||
format_args args) {
|
||||
// Legacy wide streams are not supported.
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
if (detail::is_utf8()) {
|
||||
detail::print(f, basic_string_view<Char>(buf.begin(), buf.size()));
|
||||
} else {
|
||||
buf.push_back(Char(0));
|
||||
detail::fputs(buf.data(), f);
|
||||
detail::print(f, string_view(buf.begin(), buf.size()));
|
||||
return;
|
||||
}
|
||||
buf.push_back('\0');
|
||||
int result = std::fputs(buf.data(), f);
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -566,7 +547,7 @@ OutputIt vformat_to(
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, ts, format_str, args);
|
||||
return detail::get_iterator(buf);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -645,7 +626,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts)
|
||||
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
||||
|
@ -19,84 +19,6 @@ FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||
return it + (end - begin);
|
||||
}
|
||||
|
||||
template <typename OutputIt> class truncating_iterator_base {
|
||||
protected:
|
||||
OutputIt out_;
|
||||
size_t limit_;
|
||||
size_t count_ = 0;
|
||||
|
||||
truncating_iterator_base() : out_(), limit_(0) {}
|
||||
|
||||
truncating_iterator_base(OutputIt out, size_t limit)
|
||||
: out_(out), limit_(limit) {}
|
||||
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = typename std::iterator_traits<OutputIt>::value_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
FMT_UNCHECKED_ITERATOR(truncating_iterator_base);
|
||||
|
||||
OutputIt base() const { return out_; }
|
||||
size_t count() const { return count_; }
|
||||
};
|
||||
|
||||
// An output iterator that truncates the output and counts the number of objects
|
||||
// written to it.
|
||||
template <typename OutputIt,
|
||||
typename Enable = typename std::is_void<
|
||||
typename std::iterator_traits<OutputIt>::value_type>::type>
|
||||
class truncating_iterator;
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::false_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
|
||||
|
||||
public:
|
||||
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
|
||||
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
truncating_iterator& operator++() {
|
||||
if (this->count_++ < this->limit_) ++this->out_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type& operator*() const {
|
||||
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::true_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
public:
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
template <typename T> truncating_iterator& operator=(T val) {
|
||||
if (this->count_++ < this->limit_) *this->out_++ = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator& operator++() { return *this; }
|
||||
truncating_iterator& operator++(int) { return *this; }
|
||||
truncating_iterator& operator*() { return *this; }
|
||||
};
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
|
||||
@ -196,7 +118,8 @@ template <typename Char> struct code_unit {
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, value);
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
@ -220,7 +143,12 @@ template <typename Char, typename T, int N> struct field {
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
return write<Char>(out, get_arg_checked<T, N>(args...));
|
||||
const T& arg = get_arg_checked<T, N>(args...);
|
||||
if constexpr (std::is_convertible_v<T, basic_string_view<Char>>) {
|
||||
auto s = basic_string_view<Char>(arg);
|
||||
return copy_str<Char>(s.begin(), s.end(), out);
|
||||
}
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
@ -331,14 +259,14 @@ template <typename T, typename Char> struct parse_specs_result {
|
||||
int next_arg_id;
|
||||
};
|
||||
|
||||
constexpr int manual_indexing_id = -1;
|
||||
enum { manual_indexing_id = -1 };
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos, int next_arg_id) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {},
|
||||
next_arg_id);
|
||||
auto ctx =
|
||||
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
|
||||
@ -348,22 +276,18 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
template <typename Char> struct arg_id_handler {
|
||||
arg_ref<Char> arg_id;
|
||||
|
||||
constexpr int operator()() {
|
||||
constexpr int on_auto() {
|
||||
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||
return 0;
|
||||
}
|
||||
constexpr int operator()(int id) {
|
||||
constexpr int on_index(int id) {
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
constexpr int operator()(basic_string_view<Char> id) {
|
||||
constexpr int on_name(basic_string_view<Char> id) {
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr void on_error(const char* message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct parse_arg_id_result {
|
||||
@ -452,20 +376,18 @@ constexpr auto compile_format_string(S format_str) {
|
||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||
constexpr auto arg_index =
|
||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||
if constexpr (arg_index != invalid_arg_index) {
|
||||
if constexpr (arg_index >= 0) {
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<
|
||||
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||
arg_index, next_id>(format_str);
|
||||
} else {
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
} else if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -501,7 +423,7 @@ constexpr auto compile(S format_str) {
|
||||
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
|
||||
@ -568,9 +490,10 @@ template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const S& format_str, Args&&... args) {
|
||||
auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n),
|
||||
format_str, std::forward<Args>(args)...);
|
||||
return {it.base(), it.count()};
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||
format_to(std::back_inserter(buf), format_str, std::forward<Args>(args)...);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
@ -605,7 +528,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
|
||||
} // namespace literals
|
||||
#endif
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
||||
|
1845
include/fmt/core.h
1845
include/fmt/core.h
File diff suppressed because it is too large
Load Diff
@ -9,13 +9,9 @@
|
||||
#define FMT_FORMAT_INL_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno> // errno
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstring> // std::memmove
|
||||
#include <cwchar>
|
||||
#include <exception>
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
|
||||
return '.';
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC auto write_loc(appender out, loc_value value,
|
||||
const format_specs<>& specs, locale_ref loc) -> bool {
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
auto locale = loc.get<std::locale>();
|
||||
// We cannot use the num_put<char> facet because it may produce output in
|
||||
// a wrong encoding.
|
||||
using facet = format_facet<std::locale>;
|
||||
if (std::has_facet<facet>(locale))
|
||||
return std::use_facet<facet>(locale).put(out, value, specs);
|
||||
return facet(locale).put(out, value, specs);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
#if !FMT_MSC_VERSION
|
||||
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
|
||||
template <typename Locale> typename Locale::id format_facet<Locale>::id;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
|
||||
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
|
||||
grouping_ = numpunct.grouping();
|
||||
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
|
||||
}
|
||||
|
||||
template <>
|
||||
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
|
||||
appender out, loc_value val, const format_specs<>& specs) const -> bool {
|
||||
return val.visit(
|
||||
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_});
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str,
|
||||
FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
|
||||
format_args args) {
|
||||
auto ec = std::error_code(error_code, std::generic_category());
|
||||
return std::system_error(ec, vformat(format_str, args));
|
||||
return std::system_error(ec, vformat(fmt, args));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
|
||||
return (n >> r) | (n << (64 - r));
|
||||
}
|
||||
|
||||
// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
|
||||
inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept {
|
||||
#if FMT_USE_INT128
|
||||
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
|
||||
return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};
|
||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
||||
auto result = uint128_fallback();
|
||||
result.lo_ = _umul128(x, y, &result.hi_);
|
||||
return result;
|
||||
#else
|
||||
const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>());
|
||||
|
||||
uint64_t a = x >> 32;
|
||||
uint64_t b = x & mask;
|
||||
uint64_t c = y >> 32;
|
||||
uint64_t d = y & mask;
|
||||
|
||||
uint64_t ac = a * c;
|
||||
uint64_t bc = b * c;
|
||||
uint64_t ad = a * d;
|
||||
uint64_t bd = b * d;
|
||||
|
||||
uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
|
||||
|
||||
return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
|
||||
(intermediate << 32) + (bd & mask)};
|
||||
#endif
|
||||
}
|
||||
|
||||
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
|
||||
namespace dragonbox {
|
||||
// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
|
||||
inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept {
|
||||
#if FMT_USE_INT128
|
||||
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
|
||||
return static_cast<uint64_t>(p >> 64);
|
||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
||||
return __umulh(x, y);
|
||||
#else
|
||||
return umul128(x, y).high();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a
|
||||
// 128-bit unsigned integer.
|
||||
inline uint128_fallback umul192_upper128(uint64_t x,
|
||||
uint128_fallback y) noexcept {
|
||||
uint128_fallback r = umul128(x, y.high());
|
||||
r += umul128_upper64(x, y.low());
|
||||
return r;
|
||||
}
|
||||
|
||||
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
|
||||
// 64-bit unsigned integer.
|
||||
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
|
||||
@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
|
||||
return x * y;
|
||||
}
|
||||
|
||||
// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from
|
||||
// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.
|
||||
inline int floor_log10_pow2(int e) noexcept {
|
||||
FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent");
|
||||
static_assert((-1 >> 1) == -1, "right shift is not arithmetic");
|
||||
return (e * 315653) >> 20;
|
||||
}
|
||||
|
||||
// Various fast log computations.
|
||||
inline int floor_log2_pow10(int e) noexcept {
|
||||
FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
|
||||
return (e * 1741647) >> 19;
|
||||
}
|
||||
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
|
||||
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
|
||||
return (e * 631305 - 261663) >> 21;
|
||||
}
|
||||
|
||||
static constexpr struct {
|
||||
FMT_INLINE_VARIABLE constexpr struct {
|
||||
uint32_t divisor;
|
||||
int shift_amount;
|
||||
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
|
||||
@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
|
||||
}
|
||||
|
||||
// Various subroutines using pow10 cache
|
||||
template <class T> struct cache_accessor;
|
||||
template <typename T> struct cache_accessor;
|
||||
|
||||
template <> struct cache_accessor<float> {
|
||||
using carrier_uint = float_info<float>::carrier_uint;
|
||||
@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> {
|
||||
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
|
||||
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
|
||||
{0xc5a05277621be293, 0xc7098b7305241886},
|
||||
{ 0xf70867153aa2db38,
|
||||
0xb8cbee4fc66d1ea8 }
|
||||
{0xf70867153aa2db38, 0xb8cbee4fc66d1ea8},
|
||||
{0x9a65406d44a5c903, 0x737f74f1dc043329},
|
||||
{0xc0fe908895cf3b44, 0x505f522e53053ff3},
|
||||
{0xf13e34aabb430a15, 0x647726b9e7c68ff0},
|
||||
{0x96c6e0eab509e64d, 0x5eca783430dc19f6},
|
||||
{0xbc789925624c5fe0, 0xb67d16413d132073},
|
||||
{0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890},
|
||||
{0x933e37a534cbaae7, 0x8e91b962f7b6f15a},
|
||||
{0xb80dc58e81fe95a1, 0x723627bbb5a4adb1},
|
||||
{0xe61136f2227e3b09, 0xcec3b1aaa30dd91d},
|
||||
{0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2},
|
||||
{0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e},
|
||||
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
|
||||
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
|
||||
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
|
||||
{ 0xdb68c2ca82ed2a05,
|
||||
0xa67398db9f6820e2 }
|
||||
#else
|
||||
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
|
||||
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
|
||||
@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> {
|
||||
{0x8da471a9de737e24, 0x5ceaecfed289e5d3},
|
||||
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
|
||||
{0xb8da1662e7b00a17, 0x3d6a751f3b936244},
|
||||
{ 0x95527a5202df0ccb,
|
||||
0x0f37801e0c43ebc9 }
|
||||
{0x95527a5202df0ccb, 0x0f37801e0c43ebc9},
|
||||
{0xf13e34aabb430a15, 0x647726b9e7c68ff0}
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -1138,8 +1114,12 @@ template <> struct cache_accessor<double> {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
|
||||
return cache_accessor<double>::get_cached_power(k);
|
||||
}
|
||||
|
||||
// Various integer checks
|
||||
template <class T>
|
||||
template <typename T>
|
||||
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
|
||||
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
|
||||
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
|
||||
@ -1148,12 +1128,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
|
||||
}
|
||||
|
||||
// Remove trailing zeros from n and return the number of zeros removed (float)
|
||||
FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
|
||||
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
|
||||
FMT_ASSERT(n != 0, "");
|
||||
const uint32_t mod_inv_5 = 0xcccccccd;
|
||||
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
||||
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
|
||||
constexpr uint32_t mod_inv_5 = 0xcccccccd;
|
||||
constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5
|
||||
|
||||
int s = 0;
|
||||
while (true) {
|
||||
auto q = rotr(n * mod_inv_25, 2);
|
||||
if (q > max_value<uint32_t>() / 100) break;
|
||||
@ -1165,7 +1145,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
|
||||
n = q;
|
||||
s |= 1;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -1179,32 +1158,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
|
||||
|
||||
// Is n is divisible by 10^8?
|
||||
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
|
||||
// If yes, work with the quotient.
|
||||
// If yes, work with the quotient...
|
||||
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
|
||||
|
||||
const uint32_t mod_inv_5 = 0xcccccccd;
|
||||
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
||||
|
||||
int s = 8;
|
||||
while (true) {
|
||||
auto q = rotr(n32 * mod_inv_25, 2);
|
||||
if (q > max_value<uint32_t>() / 100) break;
|
||||
n32 = q;
|
||||
s += 2;
|
||||
}
|
||||
auto q = rotr(n32 * mod_inv_5, 1);
|
||||
if (q <= max_value<uint32_t>() / 10) {
|
||||
n32 = q;
|
||||
s |= 1;
|
||||
}
|
||||
|
||||
// ... and use the 32 bit variant of the function
|
||||
int s = remove_trailing_zeros(n32, 8);
|
||||
n = n32;
|
||||
return s;
|
||||
}
|
||||
|
||||
// If n is not divisible by 10^8, work with n itself.
|
||||
const uint64_t mod_inv_5 = 0xcccccccccccccccd;
|
||||
const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
||||
constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;
|
||||
constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // = mod_inv_5 * mod_inv_5
|
||||
|
||||
int s = 0;
|
||||
while (true) {
|
||||
@ -1223,7 +1187,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
|
||||
}
|
||||
|
||||
// The main algorithm for shorter interval case
|
||||
template <class T>
|
||||
template <typename T>
|
||||
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
|
||||
decimal_fp<T> ret_value;
|
||||
// Compute k and beta
|
||||
@ -1394,17 +1358,6 @@ small_divisor_case_label:
|
||||
return ret_value;
|
||||
}
|
||||
} // namespace dragonbox
|
||||
|
||||
#ifdef _MSC_VER
|
||||
FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...)
|
||||
-> int {
|
||||
auto args = va_list();
|
||||
va_start(args, fmt);
|
||||
int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
} // namespace detail
|
||||
|
||||
template <> struct formatter<detail::bigint> {
|
||||
@ -1413,9 +1366,8 @@ template <> struct formatter<detail::bigint> {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const detail::bigint& n, FormatContext& ctx) const ->
|
||||
typename FormatContext::iterator {
|
||||
auto format(const detail::bigint& n, format_context& ctx) const
|
||||
-> format_context::iterator {
|
||||
auto out = ctx.out();
|
||||
bool first = true;
|
||||
for (auto i = n.bigits_.size(); i > 0; --i) {
|
||||
@ -1474,57 +1426,44 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN32
|
||||
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
|
||||
#else
|
||||
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
|
||||
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
|
||||
void*, const void*, dword, dword*, void*);
|
||||
|
||||
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
|
||||
auto fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
|
||||
auto written = detail::dword();
|
||||
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
|
||||
u16.c_str(), static_cast<uint32_t>(u16.size()),
|
||||
&written, nullptr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// We return false if the file descriptor was not TTY, or it was but
|
||||
// SetConsoleW failed which can happen if the output has been redirected to
|
||||
// NUL. In both cases when we return false, we should attempt to do regular
|
||||
// write via fwrite or std::ostream::write.
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||
#ifdef _WIN32
|
||||
if (write_console(f, text)) return;
|
||||
#endif
|
||||
detail::fwrite_fully(text.data(), 1, text.size(), f);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
|
||||
memory_buffer buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::print(f, {buffer.data(), buffer.size()});
|
||||
if (!_isatty(fd)) return false;
|
||||
auto u16 = utf8_to_utf16(text);
|
||||
auto written = dword();
|
||||
return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
|
||||
static_cast<uint32_t>(u16.size()), &written, nullptr) != 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Print assuming legacy (non-Unicode) encoding.
|
||||
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
|
||||
format_args args) {
|
||||
memory_buffer buffer;
|
||||
detail::vformat_to(buffer, format_str,
|
||||
FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
|
||||
auto buffer = memory_buffer();
|
||||
detail::vformat_to(buffer, fmt,
|
||||
basic_format_args<buffer_context<char>>(args));
|
||||
fwrite_fully(buffer.data(), 1, buffer.size(), f);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void vprint(string_view format_str, format_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||
if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
|
||||
auto buffer = memory_buffer();
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
detail::print(f, {buffer.data(), buffer.size()});
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(string_view fmt, format_args args) {
|
||||
vprint(stdout, fmt, args);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
1867
include/fmt/format.h
1867
include/fmt/format.h
File diff suppressed because it is too large
Load Diff
115
include/fmt/os.h
115
include/fmt/os.h
@ -71,7 +71,7 @@
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
/**
|
||||
\rst
|
||||
@ -120,51 +120,13 @@ template <typename Char> class basic_cstring_view {
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write_bytes(out, ec.category().name(),
|
||||
basic_format_specs<Char>());
|
||||
out = detail::write<Char>(out, Char(':'));
|
||||
out = detail::write<Char>(out, ec.value());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
FMT_API const std::error_category& system_category() noexcept;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
private:
|
||||
memory_buffer buffer_;
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||
size_t size() const { return buffer_.size() - 1; }
|
||||
const char* c_str() const { return &buffer_[0]; }
|
||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||
|
||||
// Performs conversion returning a system error code instead of
|
||||
// throwing exception on conversion error. This method may still throw
|
||||
// in case of memory allocation error.
|
||||
FMT_API int convert(basic_string_view<wchar_t> s);
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
const char* message) noexcept;
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
}
|
||||
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||
format_args args);
|
||||
@ -355,12 +317,18 @@ class FMT_API file {
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
buffered_file fdopen(const char* mode);
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Opens a file and constructs a file object representing this file by
|
||||
// wcstring_view filename. Windows only.
|
||||
static file open_windows_file(wcstring_view path, int oflag);
|
||||
# endif
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
struct buffer_size {
|
||||
buffer_size() = default;
|
||||
@ -397,56 +365,61 @@ struct ostream_params {
|
||||
# endif
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
class file_buffer final : public buffer<char> {
|
||||
file file_;
|
||||
|
||||
FMT_API void grow(size_t) override;
|
||||
|
||||
public:
|
||||
FMT_API file_buffer(cstring_view path, const ostream_params& params);
|
||||
FMT_API file_buffer(file_buffer&& other);
|
||||
FMT_API ~file_buffer();
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size() * sizeof(data()[0]));
|
||||
clear();
|
||||
}
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Added {} below to work around default constructor error known to
|
||||
// occur in Xcode versions 7.2.1 and 8.2.1.
|
||||
constexpr detail::buffer_size buffer_size{};
|
||||
|
||||
/** A fast output stream which is not thread-safe. */
|
||||
class FMT_API ostream final : private detail::buffer<char> {
|
||||
class FMT_API ostream {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
void grow(size_t) override;
|
||||
FMT_MSC_WARNING(suppress : 4251)
|
||||
detail::file_buffer buffer_;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
: buffer_(path, params) {}
|
||||
|
||||
public:
|
||||
ostream(ostream&& other)
|
||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
~ostream() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
clear();
|
||||
}
|
||||
~ostream();
|
||||
|
||||
void flush() { buffer_.flush(); }
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
void close() { buffer_.close(); }
|
||||
|
||||
/**
|
||||
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||
output to the file.
|
||||
*/
|
||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
||||
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) {
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
||||
|
@ -8,8 +8,8 @@
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#include <fstream> // std::filebuf
|
||||
|
||||
#if defined(_WIN32) && defined(__GLIBCXX__)
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
@ -21,48 +21,14 @@
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Checks if T has a user-defined operator<<.
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static auto test(int)
|
||||
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
|
||||
<< std::declval<U>()) != 0>;
|
||||
|
||||
template <typename> static auto test(...) -> std::false_type;
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
is_streamable() = default;
|
||||
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Formatting of built-in types and arrays is intentionally disabled because
|
||||
// it's handled by standard (non-ostream) formatters.
|
||||
template <typename T, typename Char>
|
||||
struct is_streamable<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
std::is_arithmetic<T>::value || std::is_array<T>::value ||
|
||||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
|
||||
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
|
||||
std::is_same<T, std_string_view<Char>>::value ||
|
||||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
|
||||
: std::false_type {};
|
||||
|
||||
// Generate a unique explicit instantion in every translation unit using a tag
|
||||
// type in an anonymous namespace.
|
||||
namespace {
|
||||
struct file_access_tag {};
|
||||
} // namespace
|
||||
template <class Tag, class BufType, FILE* BufType::*FileMemberPtr>
|
||||
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
|
||||
class file_access {
|
||||
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||
};
|
||||
@ -84,8 +50,8 @@ inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
|
||||
#elif defined(_WIN32) && defined(__GLIBCXX__)
|
||||
auto* rdbuf = os.rdbuf();
|
||||
FILE* c_file;
|
||||
if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||
c_file = fbuf->file();
|
||||
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||
c_file = sfbuf->file();
|
||||
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||
c_file = fbuf->file();
|
||||
else
|
||||
@ -145,7 +111,7 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
|
||||
-> OutputIt {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
format_value(buffer, value, ctx.locale());
|
||||
detail::format_value(buffer, value, ctx.locale());
|
||||
return formatter<basic_string_view<Char>, Char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
@ -180,13 +146,6 @@ auto streamed(const T& value) -> detail::streamed_view<T> {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: basic_ostream_formatter<Char> {
|
||||
using basic_ostream_formatter<Char>::format;
|
||||
};
|
||||
|
||||
inline void vprint_directly(std::ostream& os, string_view format_str,
|
||||
format_args args) {
|
||||
auto buffer = memory_buffer();
|
||||
@ -196,7 +155,7 @@ inline void vprint_directly(std::ostream& os, string_view format_str,
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT template <typename Char>
|
||||
FMT_EXPORT template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os,
|
||||
basic_string_view<type_identity_t<Char>> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
@ -215,7 +174,7 @@ void vprint(std::basic_ostream<Char>& os,
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_MODULE_EXPORT template <typename... T>
|
||||
FMT_EXPORT template <typename... T>
|
||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
const auto& vargs = fmt::make_format_args(args...);
|
||||
if (detail::is_utf8())
|
||||
@ -224,7 +183,7 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
detail::vprint_directly(os, fmt, vargs);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
FMT_EXPORT
|
||||
template <typename... Args>
|
||||
void print(std::wostream& os,
|
||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||
@ -232,6 +191,19 @@ void print(std::wostream& os,
|
||||
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
|
||||
}
|
||||
|
||||
FMT_EXPORT template <typename... T>
|
||||
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename... Args>
|
||||
void println(std::wostream& os,
|
||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||
Args&&... args) {
|
||||
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
||||
|
@ -14,24 +14,18 @@
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
template <typename Char> class basic_printf_context {
|
||||
private:
|
||||
OutputIt out_;
|
||||
detail::buffer_appender<Char> out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
|
||||
public:
|
||||
using char_type = Char;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
using parse_context_type = basic_format_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
/**
|
||||
@ -40,68 +34,68 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out,
|
||||
basic_printf_context(detail::buffer_appender<Char> out,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
auto out() -> detail::buffer_appender<Char> { return out_; }
|
||||
void advance_to(detail::buffer_appender<Char>) {}
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
auto locale() -> detail::locale_ref { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
||||
return args_.get(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
detail::error_handler().on_error(message);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
static auto fits_in_int(bool) -> bool { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
static auto fits_in_int(int) -> bool { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
struct printf_precision_handler {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
auto operator()(T value) -> int {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
throw_format_error("number is too big");
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
int operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
auto operator()(T) -> int {
|
||||
throw_format_error("precision is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
struct is_zero_int {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
auto operator()(T value) -> bool {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
auto operator()(T) -> bool {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -132,22 +126,23 @@ template <typename T, typename Context> class arg_converter {
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
auto n = static_cast<int>(static_cast<target_type>(value));
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
auto n = static_cast<long long>(value);
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
} else {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,8 +170,8 @@ template <typename Context> class char_converter {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
auto c = static_cast<typename Context::char_type>(value);
|
||||
arg_ = detail::make_arg<Context>(c);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
@ -186,122 +181,131 @@ template <typename Context> class char_converter {
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
|
||||
auto operator()(const Char* s) -> const Char* { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
private:
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
format_specs& specs_;
|
||||
format_specs<Char>& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto operator()(T value) -> unsigned {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
if (width > int_max) throw_format_error("number is too big");
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
auto operator()(T) -> unsigned {
|
||||
throw_format_error("width is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Workaround for a bug with the XL compiler when initializing
|
||||
// printf_arg_formatter's base class.
|
||||
template <typename Char>
|
||||
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
|
||||
-> arg_formatter<Char> {
|
||||
return {iter, s, locale_ref()};
|
||||
}
|
||||
|
||||
// The ``printf`` argument formatter.
|
||||
template <typename OutputIt, typename Char>
|
||||
template <typename Char>
|
||||
class printf_arg_formatter : public arg_formatter<Char> {
|
||||
private:
|
||||
using base = arg_formatter<Char>;
|
||||
using context_type = basic_printf_context<OutputIt, Char>;
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
using context_type = basic_printf_context<Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
OutputIt write_null_pointer(bool is_string = false) {
|
||||
void write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = presentation_type::none;
|
||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
public:
|
||||
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
||||
: base{iter, s, locale_ref()}, context_(ctx) {}
|
||||
printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
|
||||
context_type& ctx)
|
||||
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
||||
|
||||
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||
void operator()(monostate value) { base::operator()(value); }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
void operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||
// std::is_same instead.
|
||||
if (std::is_same<T, Char>::value) {
|
||||
format_specs fmt_specs = this->specs;
|
||||
if (fmt_specs.type != presentation_type::none &&
|
||||
fmt_specs.type != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
if (!std::is_same<T, Char>::value) {
|
||||
base::operator()(value);
|
||||
return;
|
||||
}
|
||||
return base::operator()(value);
|
||||
format_specs<Char> fmt_specs = this->specs;
|
||||
if (fmt_specs.type != presentation_type::none &&
|
||||
fmt_specs.type != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
return base::operator()(value);
|
||||
void operator()(T value) {
|
||||
base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
OutputIt operator()(const char* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
void operator()(const char* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
OutputIt operator()(const wchar_t* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
void operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
OutputIt operator()(basic_string_view<Char> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
void operator()(basic_string_view<Char> value) { base::operator()(value); }
|
||||
|
||||
/** Formats a pointer. */
|
||||
OutputIt operator()(const void* value) {
|
||||
return value ? base::operator()(value) : write_null_pointer();
|
||||
void operator()(const void* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx =
|
||||
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
||||
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx = basic_format_parse_context<Char>({});
|
||||
handle.format(parse_ctx, context_);
|
||||
return this->out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||
const Char* end) {
|
||||
void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
@ -314,9 +318,7 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
if (specs.sign != sign::plus) specs.sign = sign::space;
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
@ -328,8 +330,8 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||
}
|
||||
|
||||
template <typename Char, typename GetArg>
|
||||
int parse_header(const Char*& it, const Char* end,
|
||||
basic_format_specs<Char>& specs, GetArg get_arg) {
|
||||
auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
|
||||
GetArg get_arg) -> int {
|
||||
int arg_index = -1;
|
||||
Char c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
@ -344,7 +346,7 @@ int parse_header(const Char*& it, const Char* end,
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
if (value == -1) FMT_THROW(format_error("number is too big"));
|
||||
if (value == -1) throw_format_error("number is too big");
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
@ -355,7 +357,7 @@ int parse_header(const Char*& it, const Char* end,
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
specs.width = parse_nonnegative_int(it, end, -1);
|
||||
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
|
||||
if (specs.width == -1) throw_format_error("number is too big");
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
@ -365,13 +367,53 @@ int parse_header(const Char*& it, const Char* end,
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
inline auto parse_printf_presentation_type(char c, type t)
|
||||
-> presentation_type {
|
||||
using pt = presentation_type;
|
||||
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
|
||||
switch (c) {
|
||||
case 'd':
|
||||
return in(t, integral_set) ? pt::dec : pt::none;
|
||||
case 'o':
|
||||
return in(t, integral_set) ? pt::oct : pt::none;
|
||||
case 'x':
|
||||
return in(t, integral_set) ? pt::hex_lower : pt::none;
|
||||
case 'X':
|
||||
return in(t, integral_set) ? pt::hex_upper : pt::none;
|
||||
case 'a':
|
||||
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
|
||||
case 'A':
|
||||
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
|
||||
case 'e':
|
||||
return in(t, float_set) ? pt::exp_lower : pt::none;
|
||||
case 'E':
|
||||
return in(t, float_set) ? pt::exp_upper : pt::none;
|
||||
case 'f':
|
||||
return in(t, float_set) ? pt::fixed_lower : pt::none;
|
||||
case 'F':
|
||||
return in(t, float_set) ? pt::fixed_upper : pt::none;
|
||||
case 'g':
|
||||
return in(t, float_set) ? pt::general_lower : pt::none;
|
||||
case 'G':
|
||||
return in(t, float_set) ? pt::general_upper : pt::none;
|
||||
case 'c':
|
||||
return in(t, integral_set) ? pt::chr : pt::none;
|
||||
case 's':
|
||||
return in(t, string_set | cstring_set) ? pt::string : pt::none;
|
||||
case 'p':
|
||||
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
|
||||
default:
|
||||
return pt::none;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
using OutputIt = buffer_appender<Char>;
|
||||
auto out = OutputIt(buf);
|
||||
auto context = basic_printf_context<OutputIt, Char>(out, args);
|
||||
auto parse_ctx = basic_printf_parse_context<Char>(format);
|
||||
using iterator = buffer_appender<Char>;
|
||||
auto out = iterator(buf);
|
||||
auto context = basic_printf_context<Char>(out, args);
|
||||
auto parse_ctx = basic_format_parse_context<Char>(format);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
@ -387,26 +429,24 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
const Char* end = parse_ctx.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
if (!detail::find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
||||
if (!find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // find leaves it == nullptr if it doesn't find '%'.
|
||||
break;
|
||||
}
|
||||
Char c = *it++;
|
||||
if (it != end && *it == c) {
|
||||
out = detail::write(
|
||||
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = detail::write(out, basic_string_view<Char>(
|
||||
start, detail::to_unsigned(it - 1 - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||
|
||||
basic_format_specs<Char> specs;
|
||||
auto specs = format_specs<Char>();
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs, get_arg);
|
||||
if (arg_index == 0) parse_ctx.on_error("argument not found");
|
||||
if (arg_index == 0) throw_format_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
@ -417,7 +457,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
|
||||
visit_format_arg(printf_precision_handler(), get_arg(-1)));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
@ -426,20 +466,19 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
auto arg = get_arg(arg_index);
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral())
|
||||
specs.fill[0] =
|
||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||
if (specs.precision >= 0 && arg.is_integral()) {
|
||||
// Ignore '0' for non-numeric types or if '-' present.
|
||||
specs.fill[0] = ' ';
|
||||
}
|
||||
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
|
||||
auto str = visit_format_arg(get_cstring<Char>(), arg);
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
|
||||
basic_string_view<Char>(
|
||||
str, detail::to_unsigned(nul != str_end ? nul - str
|
||||
: specs.precision)));
|
||||
auto sv = basic_string_view<Char>(
|
||||
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
||||
arg = make_arg<basic_printf_context<Char>>(sv);
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
@ -451,7 +490,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
Char t = it != end ? *it : 0;
|
||||
using detail::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
@ -490,7 +528,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
if (it == end) throw_format_error("invalid format string");
|
||||
char type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
@ -500,32 +538,25 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(
|
||||
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
|
||||
arg);
|
||||
visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
specs.type = parse_presentation_type(type);
|
||||
specs.type = parse_printf_presentation_type(type, arg.type());
|
||||
if (specs.type == presentation_type::none)
|
||||
parse_ctx.on_error("invalid type specifier");
|
||||
throw_format_error("invalid format specifier");
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(
|
||||
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
|
||||
visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
|
||||
}
|
||||
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
}
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
using printf_context = basic_printf_context<char>;
|
||||
using wprintf_context = basic_printf_context<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
@ -542,26 +573,21 @@ inline auto make_printf_args(const T&... args)
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
// DEPRECATED!
|
||||
template <typename... T>
|
||||
inline auto make_wprintf_args(const T&... args)
|
||||
-> format_arg_store<wprintf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
template <typename Char>
|
||||
inline auto vsprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
basic_string_view<Char> fmt,
|
||||
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, detail::to_string_view(fmt), args);
|
||||
return to_string(buffer);
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, fmt, args);
|
||||
return to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -576,20 +602,19 @@ inline auto vsprintf(
|
||||
template <typename S, typename... T,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(detail::to_string_view(fmt),
|
||||
fmt::make_format_args<context>(args...));
|
||||
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
template <typename Char>
|
||||
inline auto vfprintf(
|
||||
std::FILE* f, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
std::FILE* f, basic_string_view<Char> fmt,
|
||||
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, detail::to_string_view(fmt), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, fmt, args);
|
||||
size_t size = buf.size();
|
||||
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
@ -605,17 +630,16 @@ inline auto vfprintf(
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<context>(args...));
|
||||
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
template <typename Char>
|
||||
FMT_DEPRECATED inline auto vprintf(
|
||||
basic_string_view<Char> fmt,
|
||||
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
return vfprintf(stdout, detail::to_string_view(fmt), args);
|
||||
return vfprintf(stdout, fmt, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -627,14 +651,17 @@ inline auto vprintf(
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||
return vprintf(
|
||||
detail::to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||
template <typename... T>
|
||||
inline auto printf(string_view fmt, const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_printf_args(args...));
|
||||
}
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_wprintf_args(args...));
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
||||
|
@ -22,27 +22,25 @@ FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
template <typename Range, typename OutputIt>
|
||||
auto copy(const Range& range, OutputIt out) -> OutputIt {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
template <typename OutputIt>
|
||||
auto copy(const char* str, OutputIt out) -> OutputIt {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||
template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
@ -69,7 +67,7 @@ template <typename T> class is_map {
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
#ifdef FMT_FORMAT_MAP_AS_LIST
|
||||
#ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
|
||||
static constexpr const bool value = false;
|
||||
#else
|
||||
static constexpr const bool value =
|
||||
@ -82,7 +80,7 @@ template <typename T> class is_set {
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
#ifdef FMT_FORMAT_SET_AS_LIST
|
||||
#ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
|
||||
static constexpr const bool value = false;
|
||||
#else
|
||||
static constexpr const bool value =
|
||||
@ -157,8 +155,9 @@ template <typename T>
|
||||
struct has_mutable_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||
decltype(detail::range_end(std::declval<T>())),
|
||||
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||
: std::true_type {};
|
||||
// the extra int here is because older versions of MSVC don't
|
||||
// SFINAE properly unless there are distinct types
|
||||
int>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<T, void>
|
||||
@ -211,41 +210,61 @@ class is_tuple_formattable_ {
|
||||
static constexpr const bool value = false;
|
||||
};
|
||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||
template <std::size_t... I>
|
||||
static std::true_type check2(index_sequence<I...>,
|
||||
integer_sequence<bool, (I == I)...>);
|
||||
template <std::size_t... Is>
|
||||
static std::true_type check2(index_sequence<Is...>,
|
||||
integer_sequence<bool, (Is == Is)...>);
|
||||
static std::false_type check2(...);
|
||||
template <std::size_t... I>
|
||||
template <std::size_t... Is>
|
||||
static decltype(check2(
|
||||
index_sequence<I...>{},
|
||||
index_sequence<Is...>{},
|
||||
integer_sequence<
|
||||
bool, (is_formattable<typename std::tuple_element<I, T>::type,
|
||||
C>::value)...>{})) check(index_sequence<I...>);
|
||||
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
|
||||
C>::value)...>{})) check(index_sequence<Is...>);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
decltype(check(tuple_index_sequence<T>{}))::value;
|
||||
};
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept {
|
||||
template <typename Tuple, typename F, size_t... Is>
|
||||
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
// Using a free function get<Is>(Tuple) now.
|
||||
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
|
||||
ignore_unused(unused);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
template <typename Tuple, typename F>
|
||||
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
|
||||
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
|
||||
std::forward<Tuple>(t), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
|
||||
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
|
||||
using std::get;
|
||||
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
|
||||
ignore_unused(unused);
|
||||
}
|
||||
|
||||
template <typename Tuple1, typename Tuple2, typename F>
|
||||
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
|
||||
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
|
||||
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
|
||||
std::forward<F>(f));
|
||||
}
|
||||
|
||||
namespace tuple {
|
||||
// Workaround a bug in MSVC 2019 (v140).
|
||||
template <typename Char, typename... T>
|
||||
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
|
||||
|
||||
using std::get;
|
||||
template <typename Tuple, typename Char, std::size_t... Is>
|
||||
auto get_formatters(index_sequence<Is...>)
|
||||
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
|
||||
} // namespace tuple
|
||||
|
||||
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
|
||||
// Older MSVC doesn't get the reference type correctly for arrays.
|
||||
template <typename R> struct range_reference_type_impl {
|
||||
@ -269,45 +288,37 @@ using range_reference_type =
|
||||
template <typename Range>
|
||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||
|
||||
template <typename Range>
|
||||
using uncvref_first_type =
|
||||
remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>;
|
||||
|
||||
template <typename Range>
|
||||
using uncvref_second_type = remove_cvref_t<
|
||||
decltype(std::declval<range_reference_type<Range>>().second)>;
|
||||
|
||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||
*out++ = ',';
|
||||
*out++ = ' ';
|
||||
return out;
|
||||
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 Char, typename OutputIt>
|
||||
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
|
||||
return write_escaped_string(out, str);
|
||||
}
|
||||
// These are not generic lambdas for compatibility with C++11.
|
||||
template <typename ParseContext> struct parse_empty_specs {
|
||||
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
|
||||
f.parse(ctx);
|
||||
detail::maybe_set_debug_format(f, true);
|
||||
}
|
||||
ParseContext& ctx;
|
||||
};
|
||||
template <typename FormatContext> struct format_tuple_element {
|
||||
using char_type = typename FormatContext::char_type;
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
|
||||
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
|
||||
auto sv = std_string_view<Char>(str);
|
||||
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
|
||||
}
|
||||
template <typename T>
|
||||
void operator()(const formatter<T, char_type>& f, const T& v) {
|
||||
if (i > 0)
|
||||
ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
|
||||
ctx.advance_to(f.format(v, ctx));
|
||||
++i;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||
return write_escaped_char(out, v);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||
!std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
int i;
|
||||
FormatContext& ctx;
|
||||
basic_string_view<char_type> separator;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@ -321,29 +332,20 @@ template <typename T, typename C> struct is_tuple_formattable {
|
||||
detail::is_tuple_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char,
|
||||
enable_if_t<fmt::is_tuple_like<TupleT>::value &&
|
||||
fmt::is_tuple_formattable<TupleT, Char>::value>> {
|
||||
template <typename Tuple, typename Char>
|
||||
struct formatter<Tuple, Char,
|
||||
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
|
||||
fmt::is_tuple_formattable<Tuple, Char>::value>> {
|
||||
private:
|
||||
decltype(detail::tuple::get_formatters<Tuple, Char>(
|
||||
detail::tuple_index_sequence<Tuple>())) formatters_;
|
||||
|
||||
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||
basic_string_view<Char> opening_bracket_ =
|
||||
detail::string_literal<Char, '('>{};
|
||||
basic_string_view<Char> closing_bracket_ =
|
||||
detail::string_literal<Char, ')'>{};
|
||||
|
||||
// C++11 generic lambda for format().
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) out = detail::copy_str<Char>(separator, out);
|
||||
out = detail::write_range_entry<Char>(out, v);
|
||||
++i;
|
||||
}
|
||||
int i;
|
||||
typename FormatContext::iterator& out;
|
||||
basic_string_view<Char> separator;
|
||||
};
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR formatter() {}
|
||||
|
||||
@ -359,17 +361,21 @@ struct formatter<TupleT, Char,
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it != '}')
|
||||
FMT_THROW(format_error("invalid format specifier"));
|
||||
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) const
|
||||
template <typename FormatContext>
|
||||
auto format(const Tuple& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::copy_str<Char>(opening_bracket_, out);
|
||||
detail::for_each(values, format_each<FormatContext>{0, out, separator_});
|
||||
out = detail::copy_str<Char>(closing_bracket_, out);
|
||||
return out;
|
||||
ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
|
||||
detail::for_each2(
|
||||
formatters_, value,
|
||||
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
|
||||
return detail::copy_str<Char>(closing_bracket_, ctx.out());
|
||||
}
|
||||
};
|
||||
|
||||
@ -398,12 +404,10 @@ template <typename Context> struct range_mapper {
|
||||
};
|
||||
|
||||
template <typename Char, typename Element>
|
||||
using range_formatter_type = conditional_t<
|
||||
is_formattable<Element, Char>::value,
|
||||
using range_formatter_type =
|
||||
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
|
||||
std::declval<Element>()))>,
|
||||
Char>,
|
||||
fallback_formatter<Element, Char>>;
|
||||
Char>;
|
||||
|
||||
template <typename R>
|
||||
using maybe_const_range =
|
||||
@ -413,11 +417,8 @@ using maybe_const_range =
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||
template <typename R, typename Char>
|
||||
struct is_formattable_delayed
|
||||
: disjunction<
|
||||
is_formattable<uncvref_type<maybe_const_range<R>>, Char>,
|
||||
has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {};
|
||||
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
@ -426,32 +427,16 @@ struct range_formatter;
|
||||
template <typename T, typename Char>
|
||||
struct range_formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<
|
||||
std::is_same<T, remove_cvref_t<T>>,
|
||||
disjunction<is_formattable<T, Char>,
|
||||
detail::has_fallback_formatter<T, Char>>>::value>> {
|
||||
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
|
||||
is_formattable<T, Char>>::value>> {
|
||||
private:
|
||||
detail::range_formatter_type<Char, T> underlying_;
|
||||
bool custom_specs_ = false;
|
||||
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||
basic_string_view<Char> opening_bracket_ =
|
||||
detail::string_literal<Char, '['>{};
|
||||
basic_string_view<Char> closing_bracket_ =
|
||||
detail::string_literal<Char, ']'>{};
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, int)
|
||||
-> decltype(u.set_debug_format()) {
|
||||
u.set_debug_format();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
||||
|
||||
FMT_CONSTEXPR void maybe_set_debug_format() {
|
||||
maybe_set_debug_format(underlying_, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR range_formatter() {}
|
||||
|
||||
@ -473,31 +458,24 @@ struct range_formatter<
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it == end || *it == '}') {
|
||||
maybe_set_debug_format();
|
||||
return it;
|
||||
}
|
||||
|
||||
if (*it == 'n') {
|
||||
if (it != end && *it == 'n') {
|
||||
set_brackets({}, {});
|
||||
++it;
|
||||
}
|
||||
|
||||
if (*it == '}') {
|
||||
maybe_set_debug_format();
|
||||
return it;
|
||||
if (it != end && *it != '}') {
|
||||
if (*it != ':') FMT_THROW(format_error("invalid format specifier"));
|
||||
++it;
|
||||
} else {
|
||||
detail::maybe_set_debug_format(underlying_, true);
|
||||
}
|
||||
|
||||
if (*it != ':')
|
||||
FMT_THROW(format_error("no other top-level range formatters supported"));
|
||||
|
||||
custom_specs_ = true;
|
||||
++it;
|
||||
ctx.advance_to(it);
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename R, class FormatContext>
|
||||
template <typename R, typename FormatContext>
|
||||
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
detail::range_mapper<buffer_context<Char>> mapper;
|
||||
auto out = ctx.out();
|
||||
@ -507,7 +485,6 @@ struct range_formatter<
|
||||
auto end = detail::range_end(range);
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::copy_str<Char>(separator_, out);
|
||||
;
|
||||
ctx.advance_to(out);
|
||||
out = underlying_.format(mapper.map(*it), ctx);
|
||||
++i;
|
||||
@ -520,13 +497,14 @@ struct range_formatter<
|
||||
enum class range_format { disabled, map, set, sequence, string, debug_string };
|
||||
|
||||
namespace detail {
|
||||
template <typename T> struct range_format_kind_ {
|
||||
static constexpr auto value = std::is_same<range_reference_type<T>, T>::value
|
||||
? range_format::disabled
|
||||
: is_map<T>::value ? range_format::map
|
||||
: is_set<T>::value ? range_format::set
|
||||
: range_format::sequence;
|
||||
};
|
||||
template <typename T>
|
||||
struct range_format_kind_
|
||||
: std::integral_constant<range_format,
|
||||
std::is_same<uncvref_type<T>, T>::value
|
||||
? range_format::disabled
|
||||
: is_map<T>::value ? range_format::map
|
||||
: is_set<T>::value ? range_format::set
|
||||
: range_format::sequence> {};
|
||||
|
||||
template <range_format K, typename R, typename Char, typename Enable = void>
|
||||
struct range_default_formatter;
|
||||
@ -601,9 +579,6 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||
: tuple(t), sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||
|
||||
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
||||
// support in tuple_join. It is disabled by default because of issues with
|
||||
// the dynamic width and precision.
|
||||
@ -673,7 +648,45 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
namespace detail {
|
||||
// Check if T has an interface like a container adaptor (e.g. std::stack,
|
||||
// std::queue, std::priority_queue).
|
||||
template <typename T> class is_container_adaptor_like {
|
||||
template <typename U> static auto check(U* p) -> typename U::container_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Container> struct all {
|
||||
const Container& c;
|
||||
auto begin() const -> typename Container::const_iterator { return c.begin(); }
|
||||
auto end() const -> typename Container::const_iterator { return c.end(); }
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
|
||||
bool_constant<range_format_kind<T, Char>::value ==
|
||||
range_format::disabled>>::value>>
|
||||
: formatter<detail::all<typename T::container_type>, Char> {
|
||||
using all = detail::all<typename T::container_type>;
|
||||
template <typename FormatContext>
|
||||
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
struct getter : T {
|
||||
static auto get(const T& t) -> all {
|
||||
return {t.*(&getter::c)}; // Access c through the derived class.
|
||||
}
|
||||
};
|
||||
return formatter<all>::format(getter::get(t), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
/**
|
||||
\rst
|
||||
@ -716,7 +729,7 @@ auto join(std::initializer_list<T> list, string_view sep)
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
@ -8,10 +8,18 @@
|
||||
#ifndef FMT_STD_H_
|
||||
#define FMT_STD_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "format.h"
|
||||
#include "ostream.h"
|
||||
|
||||
#if FMT_HAS_INCLUDE(<version>)
|
||||
@ -25,6 +33,30 @@
|
||||
# if FMT_HAS_INCLUDE(<variant>)
|
||||
# include <variant>
|
||||
# endif
|
||||
# if FMT_HAS_INCLUDE(<optional>)
|
||||
# include <optional>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// GCC 4 does not support FMT_HAS_INCLUDE.
|
||||
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
|
||||
# include <cxxabi.h>
|
||||
// Android NDK with gabi++ library on some architectures does not implement
|
||||
// abi::__cxa_demangle().
|
||||
# ifndef __GABIXX_CXXABI_H__
|
||||
# define FMT_HAS_ABI_CXA_DEMANGLE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Check if typeid is available.
|
||||
#ifndef FMT_USE_TYPEID
|
||||
// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI.
|
||||
# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || FMT_MSC_VERSION || \
|
||||
defined(__INTEL_RTTI__) || defined(__RTTI)
|
||||
# define FMT_USE_TYPEID 1
|
||||
# else
|
||||
# define FMT_USE_TYPEID 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_filesystem
|
||||
@ -32,21 +64,32 @@ FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> auto get_path_string(const std::filesystem::path& p) {
|
||||
return p.string<Char>();
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||
const std::filesystem::path& p) {
|
||||
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
||||
}
|
||||
|
||||
# ifdef _WIN32
|
||||
template <>
|
||||
inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted,
|
||||
const std::filesystem::path& p) {
|
||||
auto s = p.u8string();
|
||||
write_escaped_string<char>(
|
||||
std::back_inserter(quoted),
|
||||
string_view(reinterpret_cast<const char*>(s.c_str()), s.size()));
|
||||
inline auto get_path_string<char>(const std::filesystem::path& p) {
|
||||
return to_utf8<wchar_t>(p.native(), to_utf8_error_policy::replace);
|
||||
}
|
||||
# endif
|
||||
|
||||
template <>
|
||||
inline void write_escaped_path<char>(memory_buffer& quoted,
|
||||
const std::filesystem::path& p) {
|
||||
auto buf = basic_memory_buffer<wchar_t>();
|
||||
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
|
||||
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
|
||||
FMT_ASSERT(valid, "invalid utf16");
|
||||
}
|
||||
# endif // _WIN32
|
||||
|
||||
template <>
|
||||
inline void write_escaped_path<std::filesystem::path::value_type>(
|
||||
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
|
||||
@ -57,62 +100,118 @@ inline void write_escaped_path<std::filesystem::path::value_type>(
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::filesystem::path, Char>
|
||||
: formatter<basic_string_view<Char>> {
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
private:
|
||||
format_specs<Char> specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
bool debug_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||
|
||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
||||
if (it != end && *it == '?') {
|
||||
debug_ = true;
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
|
||||
typename FormatContext::iterator {
|
||||
basic_memory_buffer<Char> quoted;
|
||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
|
||||
auto specs = specs_;
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
||||
ctx);
|
||||
if (!debug_) {
|
||||
auto s = detail::get_path_string<Char>(p);
|
||||
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
||||
}
|
||||
auto quoted = basic_memory_buffer<Char>();
|
||||
detail::write_escaped_path(quoted, p);
|
||||
return formatter<basic_string_view<Char>>::format(
|
||||
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
|
||||
return detail::write(ctx.out(),
|
||||
basic_string_view<Char>(quoted.data(), quoted.size()),
|
||||
specs);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#ifdef __cpp_lib_variant
|
||||
#ifdef __cpp_lib_optional
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <typename Char> struct formatter<std::monostate, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::optional<T>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||
private:
|
||||
formatter<T, Char> underlying_;
|
||||
static constexpr basic_string_view<Char> optional =
|
||||
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
||||
'('>{};
|
||||
static constexpr basic_string_view<Char> none =
|
||||
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
|
||||
-> decltype(u.set_debug_format(set)) {
|
||||
u.set_debug_format(set);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
||||
|
||||
public:
|
||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||
maybe_set_debug_format(underlying_, true);
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::monostate&, FormatContext& ctx) const
|
||||
auto format(std::optional<T> const& opt, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
if (!opt) return detail::write<Char>(ctx.out(), none);
|
||||
|
||||
auto out = ctx.out();
|
||||
out = detail::write<Char>(out, "monostate");
|
||||
return out;
|
||||
out = detail::write<Char>(out, optional);
|
||||
ctx.advance_to(out);
|
||||
out = underlying_.format(*opt, ctx);
|
||||
return detail::write(out, ')');
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_optional
|
||||
|
||||
#ifdef __cpp_lib_variant
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
using variant_index_sequence =
|
||||
std::make_index_sequence<std::variant_size<T>::value>;
|
||||
|
||||
// variant_size and variant_alternative check.
|
||||
template <typename T, typename U = void>
|
||||
struct is_variant_like_ : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
|
||||
: std::true_type {};
|
||||
template <typename> struct is_variant_like_ : std::false_type {};
|
||||
template <typename... Types>
|
||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||
|
||||
// formattable element check
|
||||
// formattable element check.
|
||||
template <typename T, typename C> class is_variant_formattable_ {
|
||||
template <std::size_t... I>
|
||||
template <std::size_t... Is>
|
||||
static std::conjunction<
|
||||
is_formattable<std::variant_alternative_t<I, T>, C>...>
|
||||
check(std::index_sequence<I...>);
|
||||
is_formattable<std::variant_alternative_t<Is, T>, C>...>
|
||||
check(std::index_sequence<Is...>);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
@ -140,6 +239,21 @@ template <typename T, typename C> struct is_variant_formattable {
|
||||
detail::is_variant_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::monostate, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::monostate&, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return detail::write<Char>(ctx.out(), "monostate");
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Variant, typename Char>
|
||||
struct formatter<
|
||||
Variant, Char,
|
||||
@ -156,16 +270,196 @@ struct formatter<
|
||||
auto out = ctx.out();
|
||||
|
||||
out = detail::write<Char>(out, "variant(");
|
||||
std::visit(
|
||||
[&](const auto& v) {
|
||||
out = detail::write_variant_alternative<Char>(out, v);
|
||||
},
|
||||
value);
|
||||
FMT_TRY {
|
||||
std::visit(
|
||||
[&](const auto& v) {
|
||||
out = detail::write_variant_alternative<Char>(out, v);
|
||||
},
|
||||
value);
|
||||
}
|
||||
FMT_CATCH(const std::bad_variant_access&) {
|
||||
detail::write<Char>(out, "valueless by exception");
|
||||
}
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_variant
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
|
||||
out = detail::write<Char>(out, Char(':'));
|
||||
out = detail::write<Char>(out, ec.value());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||
private:
|
||||
bool with_typename_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
if (*it == 't') {
|
||||
++it;
|
||||
with_typename_ = FMT_USE_TYPEID != 0;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const std::exception& ex,
|
||||
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
|
||||
format_specs<Char> spec;
|
||||
auto out = ctx.out();
|
||||
if (!with_typename_)
|
||||
return detail::write_bytes(out, string_view(ex.what()), spec);
|
||||
|
||||
#if FMT_USE_TYPEID
|
||||
const std::type_info& ti = typeid(ex);
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
std::size_t size = 0;
|
||||
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = demangled_name_ptr.get();
|
||||
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* begin = demangled_name_ptr.get();
|
||||
char* to = begin + 5; // std::
|
||||
for (char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*to++ = *from++;
|
||||
}
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
}
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
out = detail::write_bytes(out, demangled_name_view, spec);
|
||||
# elif FMT_MSC_VERSION
|
||||
string_view demangled_name_view(ti.name());
|
||||
if (demangled_name_view.starts_with("class "))
|
||||
demangled_name_view.remove_prefix(6);
|
||||
else if (demangled_name_view.starts_with("struct "))
|
||||
demangled_name_view.remove_prefix(7);
|
||||
out = detail::write_bytes(out, demangled_name_view, spec);
|
||||
# else
|
||||
out = detail::write_bytes(out, string_view(ti.name()), spec);
|
||||
# endif
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
return detail::write_bytes(out, string_view(ex.what()), spec);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_flip : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct is_bit_reference_like {
|
||||
static constexpr const bool value =
|
||||
std::is_convertible<T, bool>::value &&
|
||||
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
|
||||
};
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
|
||||
// Workaround for libc++ incompatibility with C++ standard.
|
||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||
template <typename C>
|
||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||
static constexpr const bool value = true;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// We can't use std::vector<bool, Allocator>::reference and
|
||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||
// in partial specialization.
|
||||
FMT_EXPORT
|
||||
template <typename BitRef, typename Char>
|
||||
struct formatter<BitRef, Char,
|
||||
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||
: formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::atomic<T>, Char,
|
||||
enable_if_t<is_formattable<T, Char>::value>>
|
||||
: formatter<T, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic<T>& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<T, Char>::format(v.load(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::atomic_flag, Char>
|
||||
: formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic_flag& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v.test(), ctx);
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif // FMT_STD_H_
|
||||
|
@ -12,13 +12,32 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
# include <locale>
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
|
||||
loc_value value, const format_specs<wchar_t>& specs,
|
||||
locale_ref loc) -> bool {
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
auto& numpunct =
|
||||
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
|
||||
auto separator = std::wstring();
|
||||
auto grouping = numpunct.grouping();
|
||||
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
|
||||
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
using wstring_view = basic_string_view<wchar_t>;
|
||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||
@ -33,7 +52,9 @@ inline auto runtime(wstring_view s) -> wstring_view { return s; }
|
||||
#else
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
inline auto runtime(wstring_view s) -> basic_runtime<wchar_t> { return {{s}}; }
|
||||
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
||||
return {{s}};
|
||||
}
|
||||
#endif
|
||||
|
||||
template <> struct is_char<wchar_t> : std::true_type {};
|
||||
@ -41,9 +62,9 @@ template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||
template <> struct is_char<char16_t> : std::true_type {};
|
||||
template <> struct is_char<char32_t> : std::true_type {};
|
||||
|
||||
template <typename... Args>
|
||||
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||
const Args&... args) {
|
||||
template <typename... T>
|
||||
constexpr format_arg_store<wformat_context, T...> make_wformat_args(
|
||||
const T&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
@ -78,9 +99,9 @@ template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto vformat(basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
return to_string(buffer);
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return to_string(buf);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
@ -90,10 +111,10 @@ auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
template <typename S, typename... T, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
|
||||
return vformat(detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
@ -108,11 +129,10 @@ inline auto vformat(
|
||||
return detail::vformat(loc, detail::to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
||||
inline auto format(const Locale& loc, const S& format_str, T&&... args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
@ -126,14 +146,14 @@ auto vformat_to(OutputIt out, const S& format_str,
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, detail::to_string_view(format_str), args);
|
||||
return detail::get_iterator(buf);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
|
||||
return vformat_to(out, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
@ -149,18 +169,18 @@ inline auto vformat_to(
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
vformat_to(buf, detail::to_string_view(format_str), args,
|
||||
detail::locale_ref(loc));
|
||||
return detail::get_iterator(buf);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||
typename OutputIt, typename Locale, typename S, typename... T,
|
||||
typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
Args&&... args) ->
|
||||
T&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, loc, to_string_view(format_str),
|
||||
return vformat_to(out, loc, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
|
||||
@ -171,36 +191,36 @@ inline auto vformat_to_n(
|
||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||
n);
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
return vformat_to_n(out, n, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
template <typename S, typename... T, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||
detail::counting_buffer<Char> buf;
|
||||
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||
auto buf = detail::counting_buffer<Char>();
|
||||
detail::vformat_to(buf, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
buffer.push_back(L'\0');
|
||||
if (std::fputws(buffer.data(), f) == -1)
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, fmt, args);
|
||||
buf.push_back(L'\0');
|
||||
if (std::fputws(buf.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
@ -217,13 +237,22 @@ template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
|
||||
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||
*/
|
||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||
return format(FMT_STRING(L"{}"), value);
|
||||
}
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_XCHAR_H_
|
||||
|
69
src/fmt.cc
69
src/fmt.cc
@ -1,49 +1,44 @@
|
||||
module;
|
||||
#ifndef __cpp_modules
|
||||
# error Module not supported.
|
||||
#endif
|
||||
|
||||
// put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
// Put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module.
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <clocale>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <cwchar>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <version>
|
||||
|
||||
#if _MSC_VER
|
||||
#if __has_include(<cxxabi.h>)
|
||||
# include <cxxabi.h>
|
||||
#endif
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
@ -65,22 +60,33 @@ module;
|
||||
# endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
# if defined(__GLIBCXX__)
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
# elif defined(_LIBCPP_VERSION)
|
||||
# include <__std_stream>
|
||||
# endif
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
export module fmt;
|
||||
|
||||
#define FMT_MODULE_EXPORT export
|
||||
#define FMT_MODULE_EXPORT_BEGIN export {
|
||||
#define FMT_MODULE_EXPORT_END }
|
||||
#define FMT_BEGIN_DETAIL_NAMESPACE \
|
||||
} \
|
||||
namespace detail {
|
||||
#define FMT_END_DETAIL_NAMESPACE \
|
||||
} \
|
||||
export {
|
||||
// all library-provided declarations and definitions
|
||||
// must be in the module purview to be exported
|
||||
#define FMT_EXPORT export
|
||||
#define FMT_BEGIN_EXPORT export {
|
||||
#define FMT_END_EXPORT }
|
||||
|
||||
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
// - all declarations are detached from module 'fmt'
|
||||
// - the module behaves like a traditional static library, too
|
||||
// - all library symbols are mangled traditionally
|
||||
// - you can mix TUs with either importing or #including the {fmt} API
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
extern "C++" {
|
||||
#endif
|
||||
|
||||
// All library-provided declarations and definitions must be in the module
|
||||
// purview to be exported.
|
||||
#include "fmt/args.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
@ -88,8 +94,13 @@ export module fmt;
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/os.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "fmt/std.h"
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
}
|
||||
#endif
|
||||
|
||||
// gcc doesn't yet implement private module fragments
|
||||
#if !FMT_GCC_VERSION
|
||||
module : private;
|
||||
|
@ -28,12 +28,8 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char;
|
||||
|
||||
template FMT_API void buffer<char>::append(const char*, const char*);
|
||||
|
||||
// DEPRECATED!
|
||||
// There is no correspondent extern template in format.h because of
|
||||
// incompatibility between clang and gcc (#2377).
|
||||
template FMT_API void vformat_to(buffer<char>&, string_view,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>,
|
||||
locale_ref);
|
||||
typename vformat_args<>::type, locale_ref);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
|
165
src/os.cc
165
src/os.cc
@ -18,6 +18,10 @@
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
|
||||
# ifdef _WRS_KERNEL // VxWorks7 kernel
|
||||
# include <ioLib.h> // getpagesize
|
||||
# endif
|
||||
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
@ -72,34 +76,6 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
|
||||
length, nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
class system_message {
|
||||
@ -138,10 +114,10 @@ class utf8_system_category final : public std::error_category {
|
||||
public:
|
||||
const char* name() const noexcept override { return "system"; }
|
||||
std::string message(int error_code) const override {
|
||||
system_message msg(error_code);
|
||||
auto&& msg = system_message(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
auto utf8_message = to_utf8<wchar_t>();
|
||||
if (utf8_message.convert(msg)) {
|
||||
return utf8_message.str();
|
||||
}
|
||||
}
|
||||
@ -165,11 +141,12 @@ std::system_error vwindows_error(int err_code, string_view format_str,
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
const char* message) noexcept {
|
||||
FMT_TRY {
|
||||
system_message msg(error_code);
|
||||
auto&& msg = system_message(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
fmt::format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
|
||||
auto utf8_message = to_utf8<wchar_t>();
|
||||
if (utf8_message.convert(msg)) {
|
||||
fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
|
||||
string_view(utf8_message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -192,37 +169,47 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||
nullptr);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
|
||||
filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_) return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = nullptr;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||
}
|
||||
|
||||
int buffered_file::descriptor() const {
|
||||
#ifdef fileno // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL.
|
||||
int fd = fileno(file_);
|
||||
#else
|
||||
int fd = FMT_POSIX_CALL(fileno(file_));
|
||||
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
#endif
|
||||
if (fd == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
# ifdef _WIN32
|
||||
using mode_t = int;
|
||||
using mode_t = int;
|
||||
# endif
|
||||
constexpr mode_t mode =
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
constexpr mode_t default_open_mode =
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
|
||||
file::file(cstring_view path, int oflag) {
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
|
||||
*this = file::open_windows_file(converted.c_str(), oflag);
|
||||
# else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
# endif
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
FMT_THROW(
|
||||
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
|
||||
# endif
|
||||
}
|
||||
|
||||
file::~file() noexcept {
|
||||
@ -238,7 +225,8 @@ void file::close() {
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
@ -260,7 +248,7 @@ long long file::size() const {
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
@ -270,14 +258,16 @@ long long file::size() const {
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
@ -286,7 +276,8 @@ file file::dup(int fd) {
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
@ -294,8 +285,9 @@ void file::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
|
||||
fd_, fd));
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
|
||||
fd));
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,7 +312,8 @@ void file::pipe(file& read_end, file& write_end) {
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
# endif
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = file(fds[0]);
|
||||
@ -334,28 +327,72 @@ buffered_file file::fdopen(const char* mode) {
|
||||
# else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
# endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
if (!f) {
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot associate stream with file descriptor")));
|
||||
}
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
file file::open_windows_file(wcstring_view path, int oflag) {
|
||||
int fd = -1;
|
||||
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
|
||||
if (fd == -1) {
|
||||
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
|
||||
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
|
||||
}
|
||||
return file(fd);
|
||||
}
|
||||
# endif
|
||||
|
||||
# if !defined(__MSDOS__)
|
||||
long getpagesize() {
|
||||
# ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
# else
|
||||
# else
|
||||
# ifdef _WRS_KERNEL
|
||||
long size = FMT_POSIX_CALL(getpagesize());
|
||||
# else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
FMT_API void ostream::grow(size_t) {
|
||||
if (size < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
void file_buffer::grow(size_t) {
|
||||
if (this->size() == this->capacity()) flush();
|
||||
}
|
||||
|
||||
file_buffer::file_buffer(cstring_view path,
|
||||
const detail::ostream_params& params)
|
||||
: file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
|
||||
file_buffer::file_buffer(file_buffer&& other)
|
||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
|
||||
file_buffer::~file_buffer() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
ostream::~ostream() = default;
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
||||
|
6
support/Vagrantfile
vendored
6
support/Vagrantfile
vendored
@ -13,8 +13,8 @@ Vagrant.configure("2") do |config|
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
apt-get update
|
||||
apt-get install -y g++ make wget git
|
||||
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz
|
||||
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
|
||||
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
|
||||
wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz
|
||||
tar xzf cmake-3.26.0-Linux-x86_64.tar.gz
|
||||
ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
|
||||
SHELL
|
||||
end
|
||||
|
@ -1 +0,0 @@
|
||||
build --symlink_prefix=/ # Out of source build
|
@ -1 +1 @@
|
||||
5.1.1
|
||||
6.1.2
|
||||
|
@ -11,8 +11,8 @@ cc_library(
|
||||
"include/fmt/color.h",
|
||||
"include/fmt/compile.h",
|
||||
"include/fmt/core.h",
|
||||
"include/fmt/format-inl.h",
|
||||
"include/fmt/format.h",
|
||||
"include/fmt/format-inl.h",
|
||||
"include/fmt/os.h",
|
||||
"include/fmt/ostream.h",
|
||||
"include/fmt/printf.h",
|
||||
@ -21,7 +21,7 @@ cc_library(
|
||||
"include/fmt/xchar.h",
|
||||
],
|
||||
includes = [
|
||||
"include",
|
||||
"include",
|
||||
],
|
||||
strip_include_prefix = "include",
|
||||
visibility = ["//visibility:public"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Bazel support
|
||||
|
||||
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
|
||||
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
|
||||
|
||||
## Using {fmt} as a dependency
|
||||
|
||||
@ -37,7 +37,6 @@ git_repository(
|
||||
branch = "master",
|
||||
remote = "https://github.com/fmtlib/fmt",
|
||||
patch_cmds = [
|
||||
"mv support/bazel/.bazelrc .bazelrc",
|
||||
"mv support/bazel/.bazelversion .bazelversion",
|
||||
"mv support/bazel/BUILD.bazel BUILD.bazel",
|
||||
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
|
||||
@ -47,7 +46,6 @@ git_repository(
|
||||
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
|
||||
# Even if MSYS2 is installed the Windows related patch commands can still be used.
|
||||
patch_cmds_win = [
|
||||
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
|
||||
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
|
||||
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
|
||||
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
|
||||
@ -55,7 +53,7 @@ git_repository(
|
||||
)
|
||||
```
|
||||
|
||||
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
|
||||
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
|
||||
|
||||
*BUILD.bazel*:
|
||||
|
||||
@ -71,3 +69,6 @@ The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}.
|
||||
|
||||
To execute the binary you can run `bazel run //:Demo`.
|
||||
|
||||
# Using Bzlmod
|
||||
|
||||
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) also provides support for {fmt}.
|
||||
|
@ -11,7 +11,7 @@ buildscript {
|
||||
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
||||
//
|
||||
// Notice that 4.0.0 here is the version of [Android Gradle Plugin]
|
||||
// Accroding to URL above you will need Gradle 6.1 or higher
|
||||
// According to URL above you will need Gradle 6.1 or higher
|
||||
//
|
||||
classpath "com.android.tools.build:gradle:4.1.1"
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
# C++14 feature support detection
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
function (fmt_check_cxx_compiler_flag flag result)
|
||||
if (NOT MSVC)
|
||||
check_cxx_compiler_flag("${flag}" ${result})
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
if (NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
|
||||
|
||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
||||
fmt_check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
|
||||
fmt_check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
|
||||
|
||||
if (has_std_20_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++20)
|
||||
elseif (has_std_2a_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++2a)
|
||||
endif ()
|
||||
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 17)
|
||||
fmt_check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
|
||||
fmt_check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
|
||||
|
||||
if (has_std_17_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++17)
|
||||
elseif (has_std_1z_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1z)
|
||||
endif ()
|
||||
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 14)
|
||||
fmt_check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
|
||||
fmt_check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
|
||||
|
||||
if (has_std_14_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++14)
|
||||
elseif (has_std_1y_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1y)
|
||||
endif ()
|
||||
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 11)
|
||||
fmt_check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
|
||||
fmt_check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
|
||||
|
||||
if (has_std_11_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++11)
|
||||
elseif (has_std_0x_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++0x)
|
||||
endif ()
|
||||
endif ()
|
@ -138,6 +138,13 @@ class Translator(nodes.NodeVisitor):
|
||||
def depart_table(self, node):
|
||||
pass
|
||||
|
||||
def visit_system_message(self, node):
|
||||
pass
|
||||
|
||||
def depart_system_message(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class MDWriter(writers.Writer):
|
||||
"""GitHub-flavored markdown writer"""
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
add_subdirectory(gtest)
|
||||
|
||||
include(CheckSymbolExists)
|
||||
|
||||
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
||||
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
||||
target_include_directories(test-main PUBLIC
|
||||
@ -35,7 +37,7 @@ function(add_fmt_test name)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
|
||||
endif ()
|
||||
elseif (ADD_FMT_TEST_MODULE)
|
||||
set(libs gtest test-module)
|
||||
set(libs test-main test-module)
|
||||
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
|
||||
else ()
|
||||
set(libs test-main fmt)
|
||||
@ -53,6 +55,10 @@ function(add_fmt_test name)
|
||||
add_test(NAME ${name} COMMAND ${name})
|
||||
endfunction()
|
||||
|
||||
if (FMT_MODULE)
|
||||
return ()
|
||||
endif ()
|
||||
|
||||
add_fmt_test(args-test)
|
||||
add_fmt_test(assert-test)
|
||||
add_fmt_test(chrono-test)
|
||||
@ -75,7 +81,13 @@ if (MSVC)
|
||||
endif()
|
||||
add_fmt_test(printf-test)
|
||||
add_fmt_test(ranges-test ranges-odr-test.cc)
|
||||
|
||||
add_fmt_test(scan-test)
|
||||
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
|
||||
if (HAVE_STRPTIME)
|
||||
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
|
||||
endif ()
|
||||
|
||||
add_fmt_test(std-test)
|
||||
try_compile(compile_result_unused
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
@ -94,13 +106,13 @@ add_fmt_test(enforce-checks-test)
|
||||
target_compile_definitions(enforce-checks-test PRIVATE
|
||||
-DFMT_ENFORCE_COMPILE_STRING)
|
||||
|
||||
if (FMT_CAN_MODULE)
|
||||
if (FMT_MODULE)
|
||||
# The tests need {fmt} to be compiled as traditional library
|
||||
# because of visibility of implementation details.
|
||||
# If module support is present the module tests require a
|
||||
# test-only module to be built from {fmt}
|
||||
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
|
||||
target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES})
|
||||
target_compile_features(test-module PUBLIC cxx_std_11)
|
||||
target_include_directories(test-module PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
enable_module(test-module)
|
||||
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
cmake_minimum_required(VERSION 3.8...3.25)
|
||||
|
||||
project(fmt-test CXX)
|
||||
|
||||
|
@ -44,7 +44,7 @@ template <> struct formatter<custom_type> {
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return format_to(ctx.out(), "cust={}", p.i);
|
||||
return fmt::format_to(ctx.out(), "cust={}", p.i);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "fmt/chrono.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
|
||||
@ -14,9 +15,15 @@
|
||||
#include "util.h" // get_locale
|
||||
|
||||
using fmt::runtime;
|
||||
|
||||
using testing::Contains;
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
# define FMT_HAS_C99_STRFTIME 0
|
||||
#else
|
||||
# define FMT_HAS_C99_STRFTIME 1
|
||||
#endif
|
||||
|
||||
auto make_tm() -> std::tm {
|
||||
auto time = std::tm();
|
||||
time.tm_mday = 1;
|
||||
@ -122,7 +129,7 @@ TEST(chrono_test, format_tm) {
|
||||
make_tm(2000, 1, 3, 12, 14, 16) // W1
|
||||
};
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||
#if !FMT_HAS_C99_STRFTIME
|
||||
GTEST_SKIP() << "Skip the rest of this test because it relies on strftime() "
|
||||
"conforming to C99, but on this platform, MINGW + MSVCRT, "
|
||||
"the function conforms only to C89.";
|
||||
@ -234,32 +241,135 @@ auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
|
||||
lhs.tm_isdst == rhs.tm_isdst;
|
||||
}
|
||||
|
||||
TEST(chrono_test, localtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
||||
}
|
||||
|
||||
TEST(chrono_test, gmtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::gmtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
||||
}
|
||||
|
||||
template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string {
|
||||
template <typename TimePoint>
|
||||
auto strftime_full_utc(TimePoint tp) -> std::string {
|
||||
auto t = std::chrono::system_clock::to_time_t(tp);
|
||||
auto tm = *std::gmtime(&t);
|
||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||
}
|
||||
|
||||
TEST(chrono_test, system_clock_time_point) {
|
||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now());
|
||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
|
||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
|
||||
using time_point =
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
|
||||
auto t2 = time_point(std::chrono::seconds(42));
|
||||
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||
|
||||
std::vector<std::string> spec_list = {
|
||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
||||
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||
"%EX", "%D", "%F", "%R", "%T", "%p"};
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows because these formats are not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
#elif !FMT_HAS_C99_STRFTIME
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d",
|
||||
"%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"};
|
||||
#endif
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
auto sys_output = system_strftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
|
||||
// Timezone formatters tests makes sense for localtime.
|
||||
#if FMT_HAS_C99_STRFTIME
|
||||
spec_list = {"%z", "%Z"};
|
||||
#else
|
||||
spec_list = {"%Z"};
|
||||
#endif
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_strftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
|
||||
if (spec == "%z") {
|
||||
sys_output.insert(sys_output.end() - 2, 1, ':');
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
||||
// the timezone in std::tm (if it presents on platform).
|
||||
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
std::vector<std::string> tz_names = {"GMT", "UTC"};
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
|
||||
}
|
||||
|
||||
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
EXPECT_EQ("+0000", fmt::format("{:%z}", t1));
|
||||
EXPECT_EQ("+0000", fmt::format("{:%z}", tm));
|
||||
|
||||
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", t1));
|
||||
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm));
|
||||
|
||||
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1));
|
||||
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
#if FMT_USE_LOCAL_TIME
|
||||
|
||||
TEST(chrono_test, localtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
|
||||
auto t = std::chrono::system_clock::to_time_t(
|
||||
std::chrono::current_zone()->to_sys(tp));
|
||||
auto tm = *std::localtime(&t);
|
||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||
}
|
||||
|
||||
TEST(chrono_test, time_point) {
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||
EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1));
|
||||
using time_point =
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
|
||||
auto t2 = time_point(std::chrono::seconds(42));
|
||||
EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||
TEST(chrono_test, local_system_clock_time_point) {
|
||||
# ifdef _WIN32
|
||||
return; // Not supported on Windows.
|
||||
# endif
|
||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
|
||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
|
||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
|
||||
using time_point = std::chrono::local_time<std::chrono::seconds>;
|
||||
auto t2 = time_point(std::chrono::seconds(86400 + 42));
|
||||
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||
|
||||
std::vector<std::string> spec_list = {
|
||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||
@ -268,19 +378,20 @@ TEST(chrono_test, time_point) {
|
||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
|
||||
#ifndef _WIN32
|
||||
# ifndef _WIN32
|
||||
// Disabled on Windows because these formats are not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
#elif defined(__MINGW32__) && !defined(_UCRT)
|
||||
# elif !FMT_HAS_C99_STRFTIME
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
|
||||
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
|
||||
#endif
|
||||
# endif
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto t = std::chrono::system_clock::to_time_t(
|
||||
std::chrono::current_zone()->to_sys(t1));
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_strftime(spec, &tm);
|
||||
@ -289,8 +400,26 @@ TEST(chrono_test, time_point) {
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
|
||||
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
|
||||
spec_list.cend()) {
|
||||
auto t = std::chrono::system_clock::to_time_t(
|
||||
std::chrono::current_zone()->to_sys(t1));
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_strftime("%z", &tm);
|
||||
sys_output.insert(sys_output.end() - 2, 1, ':');
|
||||
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
||||
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FMT_USE_LOCAL_TIME
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
TEST(chrono_test, format_default) {
|
||||
@ -335,7 +464,7 @@ TEST(chrono_test, format_default) {
|
||||
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
||||
}
|
||||
|
||||
TEST(chrono_test, align) {
|
||||
TEST(chrono_test, duration_align) {
|
||||
auto s = std::chrono::seconds(42);
|
||||
EXPECT_EQ("42s ", fmt::format("{:5}", s));
|
||||
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
|
||||
@ -351,6 +480,35 @@ TEST(chrono_test, align) {
|
||||
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
||||
}
|
||||
|
||||
TEST(chrono_test, tm_align) {
|
||||
auto t = make_tm(1975, 12, 29, 12, 14, 16);
|
||||
EXPECT_EQ("1975-12-29 12:14:16", fmt::format("{:%F %T}", t));
|
||||
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:30%F %T}", t));
|
||||
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:{}%F %T}", t, 30));
|
||||
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:<30%F %T}", t));
|
||||
EXPECT_EQ(" 1975-12-29 12:14:16 ", fmt::format("{:^30%F %T}", t));
|
||||
EXPECT_EQ(" 1975-12-29 12:14:16", fmt::format("{:>30%F %T}", t));
|
||||
|
||||
EXPECT_EQ("1975-12-29 12:14:16***********", fmt::format("{:*<30%F %T}", t));
|
||||
EXPECT_EQ("*****1975-12-29 12:14:16******", fmt::format("{:*^30%F %T}", t));
|
||||
EXPECT_EQ("***********1975-12-29 12:14:16", fmt::format("{:*>30%F %T}", t));
|
||||
}
|
||||
|
||||
TEST(chrono_test, tp_align) {
|
||||
auto tp = std::chrono::time_point_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(0));
|
||||
EXPECT_EQ("00:00.000000", fmt::format("{:%M:%S}", tp));
|
||||
EXPECT_EQ("00:00.000000 ", fmt::format("{:15%M:%S}", tp));
|
||||
EXPECT_EQ("00:00.000000 ", fmt::format("{:{}%M:%S}", tp, 15));
|
||||
EXPECT_EQ("00:00.000000 ", fmt::format("{:<15%M:%S}", tp));
|
||||
EXPECT_EQ(" 00:00.000000 ", fmt::format("{:^15%M:%S}", tp));
|
||||
EXPECT_EQ(" 00:00.000000", fmt::format("{:>15%M:%S}", tp));
|
||||
|
||||
EXPECT_EQ("00:00.000000***", fmt::format("{:*<15%M:%S}", tp));
|
||||
EXPECT_EQ("*00:00.000000**", fmt::format("{:*^15%M:%S}", tp));
|
||||
EXPECT_EQ("***00:00.000000", fmt::format("{:*>15%M:%S}", tp));
|
||||
}
|
||||
|
||||
TEST(chrono_test, format_specs) {
|
||||
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
|
||||
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
|
||||
@ -418,6 +576,10 @@ TEST(chrono_test, invalid_specs) {
|
||||
"invalid format");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
|
||||
"invalid format");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:abc}"), sec), fmt::format_error,
|
||||
"invalid format");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2f}"), sec), fmt::format_error,
|
||||
"invalid format");
|
||||
}
|
||||
|
||||
auto format_tm(const std::tm& time, fmt::string_view spec,
|
||||
@ -465,7 +627,7 @@ TEST(chrono_test, format_default_fp) {
|
||||
|
||||
TEST(chrono_test, format_precision) {
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
|
||||
(void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
|
||||
fmt::format_error, "precision not allowed for this argument type");
|
||||
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
|
||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||
@ -568,9 +730,9 @@ TEST(chrono_test, special_durations) {
|
||||
"03:33");
|
||||
EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
|
||||
"03:33:20");
|
||||
EXPECT_EQ("44.000000000000",
|
||||
fmt::format("{:%S}", std::chrono::duration<float, std::pico>(
|
||||
1.54213895E+26)));
|
||||
EXPECT_EQ("01.234",
|
||||
fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(
|
||||
1.234e12)));
|
||||
}
|
||||
|
||||
TEST(chrono_test, unsigned_duration) {
|
||||
@ -610,12 +772,28 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
||||
"-13.420148734");
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
|
||||
// Check subsecond presision modifier.
|
||||
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}),
|
||||
"00.000001");
|
||||
EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}),
|
||||
"00.000001234000000000");
|
||||
EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6),
|
||||
"00.000001");
|
||||
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}),
|
||||
"01.234000");
|
||||
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
|
||||
"-01.234000");
|
||||
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
|
||||
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
|
||||
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
|
||||
EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400");
|
||||
{
|
||||
// Check that {:%H:%M:%S} is equivalent to {:%T}.
|
||||
auto dur = std::chrono::milliseconds{3601234};
|
||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||
EXPECT_EQ(formatted_dur, "01:00:01.234");
|
||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
||||
}
|
||||
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
||||
@ -629,15 +807,197 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
|
||||
}
|
||||
// Check that durations with precision greater than std::chrono::seconds have
|
||||
// fixed precision, and print zeros even if there is no fractional part.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
||||
"07.000000");
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
||||
EXPECT_EQ(fmt::format("{:%S}",
|
||||
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
||||
"00.333333");
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)),
|
||||
EXPECT_EQ(fmt::format("{:%S}",
|
||||
std::chrono::duration<long long, std::ratio<1, 7>>(1)),
|
||||
"00.142857");
|
||||
|
||||
EXPECT_EQ(
|
||||
fmt::format("{:%S}",
|
||||
std::chrono::duration<signed char, std::ratio<1, 100>>(0x80)),
|
||||
"-01.28");
|
||||
|
||||
EXPECT_EQ(
|
||||
fmt::format("{:%M:%S}",
|
||||
std::chrono::duration<short, std::ratio<1, 100>>(0x8000)),
|
||||
"-05:27.68");
|
||||
|
||||
// Check that floating point seconds with ratio<1,1> are printed.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
|
||||
"01.500000");
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
|
||||
"-01:01.250000");
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
|
||||
// (time zone database) is not shipped with many windows versions.
|
||||
#if FMT_USE_UTC_TIME && !defined(_WIN32)
|
||||
TEST(chrono_test, utc_clock) {
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
auto t1_utc = std::chrono::utc_clock::from_sys(t1);
|
||||
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t1),
|
||||
fmt::format("{:%Y-%m-%d %H:%M:%S}", t1_utc));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(chrono_test, timestamps_ratios) {
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
|
||||
t1(std::chrono::milliseconds(67890));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", t1), "01:07.890");
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>
|
||||
t2(std::chrono::minutes(7));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", t2), "07:00");
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::duration<int, std::ratio<9>>>
|
||||
t3(std::chrono::duration<int, std::ratio<9>>(7));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", t3), "01:03");
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::duration<int, std::ratio<63>>>
|
||||
t4(std::chrono::duration<int, std::ratio<63>>(1));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03");
|
||||
}
|
||||
|
||||
TEST(chrono_test, timestamps_sub_seconds) {
|
||||
std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::duration<long long, std::ratio<1, 3>>>
|
||||
t1(std::chrono::duration<long long, std::ratio<1, 3>>(4));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t1), "01.333333");
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::duration<double, std::ratio<1, 3>>>
|
||||
t2(std::chrono::duration<double, std::ratio<1, 3>>(4));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t2), "01.333333");
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
|
||||
t3(std::chrono::seconds(2));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t3), "02");
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::duration<double>>
|
||||
t4(std::chrono::duration<double, std::ratio<1, 1>>(9.5));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t4), "09.500000");
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::duration<double>>
|
||||
t5(std::chrono::duration<double, std::ratio<1, 1>>(9));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t5), "09");
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds>
|
||||
t6(std::chrono::seconds(1) + std::chrono::milliseconds(120));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t6), "01.120");
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::microseconds>
|
||||
t7(std::chrono::microseconds(1234567));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t7), "01.234567");
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::nanoseconds>
|
||||
t8(std::chrono::nanoseconds(123456789));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
|
||||
|
||||
const auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now());
|
||||
const auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
|
||||
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
|
||||
|
||||
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
||||
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
|
||||
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
||||
fmt::format("{:%Y-%m-%d %T}", t9));
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds>
|
||||
t10(std::chrono::milliseconds(2000));
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
|
||||
|
||||
{
|
||||
const auto epoch = std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::milliseconds>();
|
||||
const auto d = std::chrono::milliseconds(250);
|
||||
|
||||
EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - d));
|
||||
EXPECT_EQ("00.000", fmt::format("{:%S}", epoch));
|
||||
EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(chrono_test, glibc_extensions) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()),
|
||||
fmt::format_error, "invalid format");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()),
|
||||
fmt::format_error, "invalid format");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()),
|
||||
fmt::format_error, "invalid format");
|
||||
|
||||
{
|
||||
const auto d = std::chrono::hours(1) + std::chrono::minutes(2) +
|
||||
std::chrono::seconds(3);
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3");
|
||||
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3");
|
||||
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3");
|
||||
}
|
||||
|
||||
{
|
||||
const auto tm = make_tm(1970, 1, 1, 1, 2, 3);
|
||||
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3");
|
||||
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03");
|
||||
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3");
|
||||
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3");
|
||||
}
|
||||
|
||||
{
|
||||
const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140);
|
||||
EXPECT_EQ(fmt::format("{:%S}", d), "03.140");
|
||||
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140");
|
||||
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140");
|
||||
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140");
|
||||
}
|
||||
|
||||
{
|
||||
const auto d = std::chrono::duration<double>(3.14);
|
||||
EXPECT_EQ(fmt::format("{:%S}", d), "03.140000");
|
||||
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000");
|
||||
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000");
|
||||
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Test if compile errors are produced where necessary.
|
||||
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
cmake_minimum_required(VERSION 3.8...3.25)
|
||||
project(compile-error-test CXX)
|
||||
|
||||
set(fmt_headers "
|
||||
@ -64,7 +64,7 @@ function (run_tests)
|
||||
")
|
||||
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
cmake_minimum_required(VERSION 3.8...3.25)
|
||||
project(tests CXX)
|
||||
add_subdirectory(${FMT_DIR} fmt)
|
||||
${cmake_targets}
|
||||
|
@ -12,6 +12,7 @@
|
||||
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
|
||||
defined(__cpp_constexpr_dynamic_alloc) && \
|
||||
__cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L
|
||||
|
||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
||||
|
@ -21,38 +21,6 @@ TEST(iterator_test, counting_iterator) {
|
||||
EXPECT_EQ((it + 41).count(), 42);
|
||||
}
|
||||
|
||||
TEST(iterator_test, truncating_iterator) {
|
||||
char* p = nullptr;
|
||||
auto it = fmt::detail::truncating_iterator<char*>(p, 3);
|
||||
auto prev = it++;
|
||||
EXPECT_EQ(prev.base(), p);
|
||||
EXPECT_EQ(it.base(), p + 1);
|
||||
}
|
||||
|
||||
TEST(iterator_test, truncating_iterator_default_construct) {
|
||||
auto it = fmt::detail::truncating_iterator<char*>();
|
||||
EXPECT_EQ(nullptr, it.base());
|
||||
EXPECT_EQ(std::size_t{0}, it.count());
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_ranges
|
||||
TEST(iterator_test, truncating_iterator_is_output_iterator) {
|
||||
static_assert(
|
||||
std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(iterator_test, truncating_back_inserter) {
|
||||
auto buffer = std::string();
|
||||
auto bi = std::back_inserter(buffer);
|
||||
auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
|
||||
*it++ = '4';
|
||||
*it++ = '2';
|
||||
*it++ = '1';
|
||||
EXPECT_EQ(buffer.size(), 2);
|
||||
EXPECT_EQ(buffer, "42");
|
||||
}
|
||||
|
||||
TEST(compile_test, compile_fallback) {
|
||||
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
||||
// not available.
|
||||
@ -227,14 +195,27 @@ TEST(compile_test, format_to_n) {
|
||||
EXPECT_STREQ("2a", buffer);
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_bit_cast
|
||||
# ifdef __cpp_lib_bit_cast
|
||||
TEST(compile_test, constexpr_formatted_size) {
|
||||
FMT_CONSTEXPR20 size_t s1 = fmt::formatted_size(FMT_COMPILE("{0}"), 42);
|
||||
EXPECT_EQ(2, s1);
|
||||
FMT_CONSTEXPR20 size_t s2 = fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0);
|
||||
EXPECT_EQ(5, s2);
|
||||
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
||||
EXPECT_EQ(size, 2);
|
||||
FMT_CONSTEXPR20 size_t hex_size =
|
||||
fmt::formatted_size(FMT_COMPILE("{:x}"), 15);
|
||||
EXPECT_EQ(hex_size, 1);
|
||||
FMT_CONSTEXPR20 size_t binary_size =
|
||||
fmt::formatted_size(FMT_COMPILE("{:b}"), 15);
|
||||
EXPECT_EQ(binary_size, 4);
|
||||
FMT_CONSTEXPR20 size_t padded_size =
|
||||
fmt::formatted_size(FMT_COMPILE("{:*^6}"), 42);
|
||||
EXPECT_EQ(padded_size, 6);
|
||||
FMT_CONSTEXPR20 size_t float_size =
|
||||
fmt::formatted_size(FMT_COMPILE("{:.3}"), 12.345);
|
||||
EXPECT_EQ(float_size, 4);
|
||||
FMT_CONSTEXPR20 size_t str_size =
|
||||
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
|
||||
EXPECT_EQ(str_size, 3);
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
|
||||
TEST(compile_test, text_and_arg) {
|
||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||
@ -304,8 +285,9 @@ TEST(compile_test, compile_format_string_literal) {
|
||||
// (compiler file
|
||||
// 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp',
|
||||
// line 8635)
|
||||
#if ((FMT_CPLUSPLUS >= 202002L) && \
|
||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
|
||||
#if ((FMT_CPLUSPLUS >= 202002L) && \
|
||||
(!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \
|
||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
|
||||
(FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
|
||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||
|
@ -9,9 +9,7 @@
|
||||
#include "test-assert.h"
|
||||
// clang-format on
|
||||
|
||||
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
|
||||
#include "fmt/core.h"
|
||||
#undef I
|
||||
|
||||
#include <algorithm> // std::copy_n
|
||||
#include <climits> // INT_MAX
|
||||
@ -72,6 +70,16 @@ TEST(string_view_test, compare) {
|
||||
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
||||
EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
|
||||
EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
|
||||
|
||||
EXPECT_TRUE(string_view("foo").starts_with('f'));
|
||||
EXPECT_FALSE(string_view("foo").starts_with('o'));
|
||||
EXPECT_FALSE(string_view().starts_with('o'));
|
||||
|
||||
EXPECT_TRUE(string_view("foo").starts_with("fo"));
|
||||
EXPECT_TRUE(string_view("foo").starts_with("foo"));
|
||||
EXPECT_FALSE(string_view("foo").starts_with("fooo"));
|
||||
EXPECT_FALSE(string_view().starts_with("fooo"));
|
||||
|
||||
check_op<std::equal_to>();
|
||||
check_op<std::not_equal_to>();
|
||||
check_op<std::less>();
|
||||
@ -80,24 +88,6 @@ TEST(string_view_test, compare) {
|
||||
check_op<std::greater_equal>();
|
||||
}
|
||||
|
||||
namespace test_ns {
|
||||
template <typename Char> class test_string {
|
||||
private:
|
||||
std::basic_string<Char> s_;
|
||||
|
||||
public:
|
||||
test_string(const Char* s) : s_(s) {}
|
||||
const Char* data() const { return s_.data(); }
|
||||
size_t length() const { return s_.size(); }
|
||||
operator const Char*() const { return s_.c_str(); }
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
|
||||
return {s.data(), s.length()};
|
||||
}
|
||||
} // namespace test_ns
|
||||
|
||||
TEST(core_test, is_output_iterator) {
|
||||
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
|
||||
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
|
||||
@ -151,7 +141,7 @@ TEST(buffer_test, indestructible) {
|
||||
}
|
||||
|
||||
template <typename T> struct mock_buffer final : buffer<T> {
|
||||
MOCK_METHOD1(do_grow, size_t(size_t capacity));
|
||||
MOCK_METHOD(size_t, do_grow, (size_t));
|
||||
|
||||
void grow(size_t capacity) override {
|
||||
this->set(this->data(), do_grow(capacity));
|
||||
@ -283,7 +273,7 @@ struct custom_context {
|
||||
bool called = false;
|
||||
|
||||
template <typename T> struct formatter_type {
|
||||
auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
@ -300,11 +290,11 @@ struct test_struct {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <typename Char> struct formatter<test_struct, Char> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) {
|
||||
auto format(test_struct, format_context& ctx) const -> decltype(ctx.out()) {
|
||||
auto test = string_view("test");
|
||||
return std::copy_n(test.data(), test.size(), ctx.out());
|
||||
}
|
||||
@ -318,7 +308,7 @@ TEST(arg_test, format_args) {
|
||||
|
||||
TEST(arg_test, make_value_with_custom_context) {
|
||||
auto t = test_struct();
|
||||
fmt::detail::value<custom_context> arg(
|
||||
auto arg = fmt::detail::value<custom_context>(
|
||||
fmt::detail::arg_mapper<custom_context>().map(t));
|
||||
auto ctx = custom_context();
|
||||
auto parse_ctx = fmt::format_parse_context("");
|
||||
@ -337,12 +327,12 @@ template <typename T> struct mock_visitor {
|
||||
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
|
||||
}
|
||||
|
||||
MOCK_METHOD1_T(visit, test_result(T value));
|
||||
MOCK_METHOD0_T(unexpected, void());
|
||||
MOCK_METHOD(test_result, visit, (T));
|
||||
MOCK_METHOD(void, unexpected, ());
|
||||
|
||||
test_result operator()(T value) { return visit(value); }
|
||||
auto operator()(T value) -> test_result { return visit(value); }
|
||||
|
||||
template <typename U> test_result operator()(U) {
|
||||
template <typename U> auto operator()(U) -> test_result {
|
||||
unexpected();
|
||||
return test_result();
|
||||
}
|
||||
@ -371,10 +361,11 @@ VISIT_TYPE(unsigned long, unsigned long long);
|
||||
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
||||
EXPECT_CALL(visitor, visit(expected)); \
|
||||
using iterator = std::back_insert_iterator<buffer<Char>>; \
|
||||
auto var = value; \
|
||||
fmt::visit_format_arg( \
|
||||
visitor, \
|
||||
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
|
||||
value)); \
|
||||
var)); \
|
||||
}
|
||||
|
||||
#define CHECK_ARG_SIMPLE(value) \
|
||||
@ -394,13 +385,13 @@ using test_types =
|
||||
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
||||
|
||||
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||
T test_value() {
|
||||
auto test_value() -> T {
|
||||
return static_cast<T>(42);
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
|
||||
T test_value() {
|
||||
auto test_value() -> T {
|
||||
return static_cast<T>(4.2);
|
||||
}
|
||||
|
||||
@ -443,8 +434,8 @@ TEST(arg_test, pointer_arg) {
|
||||
}
|
||||
|
||||
struct check_custom {
|
||||
test_result operator()(
|
||||
fmt::basic_format_arg<fmt::format_context>::handle h) const {
|
||||
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
|
||||
-> test_result {
|
||||
struct test_buffer final : fmt::detail::buffer<char> {
|
||||
char data[10];
|
||||
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
|
||||
@ -463,13 +454,13 @@ TEST(arg_test, custom_arg) {
|
||||
auto test = test_struct();
|
||||
using visitor =
|
||||
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
|
||||
testing::StrictMock<visitor> v;
|
||||
auto&& v = testing::StrictMock<visitor>();
|
||||
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
|
||||
fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
|
||||
}
|
||||
|
||||
TEST(arg_test, visit_invalid_arg) {
|
||||
testing::StrictMock<mock_visitor<fmt::monostate>> visitor;
|
||||
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
|
||||
EXPECT_CALL(visitor, visit(_));
|
||||
auto arg = fmt::basic_format_arg<fmt::format_context>();
|
||||
fmt::visit_format_arg(visitor, arg);
|
||||
@ -477,186 +468,78 @@ TEST(arg_test, visit_invalid_arg) {
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
|
||||
enum class arg_id_result { none, empty, index, name, error };
|
||||
enum class arg_id_result { none, empty, index, name };
|
||||
struct test_arg_id_handler {
|
||||
arg_id_result res = arg_id_result::none;
|
||||
int index = 0;
|
||||
string_view name;
|
||||
|
||||
constexpr void operator()() { res = arg_id_result::empty; }
|
||||
constexpr void on_auto() { res = arg_id_result::empty; }
|
||||
|
||||
constexpr void operator()(int i) {
|
||||
constexpr void on_index(int i) {
|
||||
res = arg_id_result::index;
|
||||
index = i;
|
||||
}
|
||||
|
||||
constexpr void operator()(string_view n) {
|
||||
constexpr void on_name(string_view n) {
|
||||
res = arg_id_result::name;
|
||||
name = n;
|
||||
}
|
||||
|
||||
constexpr void on_error(const char*) { res = arg_id_result::error; }
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
|
||||
test_arg_id_handler h;
|
||||
auto h = test_arg_id_handler();
|
||||
fmt::detail::parse_arg_id(s, s + N, h);
|
||||
return h;
|
||||
}
|
||||
|
||||
TEST(format_test, constexpr_parse_arg_id) {
|
||||
TEST(core_test, constexpr_parse_arg_id) {
|
||||
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
|
||||
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
|
||||
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
|
||||
static_assert(parse_arg_id("42:").index == 42, "");
|
||||
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
|
||||
static_assert(parse_arg_id("foo:").name.size() == 3, "");
|
||||
static_assert(parse_arg_id("!").res == arg_id_result::error, "");
|
||||
}
|
||||
|
||||
struct test_format_specs_handler {
|
||||
enum result { none, hash, zero, loc, error };
|
||||
result res = none;
|
||||
|
||||
fmt::align_t alignment = fmt::align::none;
|
||||
fmt::sign_t sign = fmt::sign::none;
|
||||
char fill = 0;
|
||||
int width = 0;
|
||||
fmt::detail::arg_ref<char> width_ref;
|
||||
int precision = 0;
|
||||
fmt::detail::arg_ref<char> precision_ref;
|
||||
fmt::presentation_type type = fmt::presentation_type::none;
|
||||
|
||||
// Workaround for MSVC2017 bug that results in "expression did not evaluate
|
||||
// to a constant" with compiler-generated copy ctor.
|
||||
constexpr test_format_specs_handler() {}
|
||||
constexpr test_format_specs_handler(const test_format_specs_handler& other) =
|
||||
default;
|
||||
|
||||
constexpr void on_align(fmt::align_t a) { alignment = a; }
|
||||
constexpr void on_fill(fmt::string_view f) { fill = f[0]; }
|
||||
constexpr void on_sign(fmt::sign_t s) { sign = s; }
|
||||
constexpr void on_hash() { res = hash; }
|
||||
constexpr void on_zero() { res = zero; }
|
||||
constexpr void on_localized() { res = loc; }
|
||||
|
||||
constexpr void on_width(int w) { width = w; }
|
||||
constexpr void on_dynamic_width(fmt::detail::auto_id) {}
|
||||
constexpr void on_dynamic_width(int index) { width_ref = index; }
|
||||
constexpr void on_dynamic_width(string_view) {}
|
||||
|
||||
constexpr void on_precision(int p) { precision = p; }
|
||||
constexpr void on_dynamic_precision(fmt::detail::auto_id) {}
|
||||
constexpr void on_dynamic_precision(int index) { precision_ref = index; }
|
||||
constexpr void on_dynamic_precision(string_view) {}
|
||||
|
||||
constexpr void end_precision() {}
|
||||
constexpr void on_type(fmt::presentation_type t) { type = t; }
|
||||
constexpr void on_error(const char*) { res = error; }
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) {
|
||||
auto h = test_format_specs_handler();
|
||||
fmt::detail::parse_format_specs(s, s + N - 1, h);
|
||||
return h;
|
||||
template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
|
||||
auto ctx = fmt::detail::compile_parse_context<char>(fmt::string_view(s, N),
|
||||
43, nullptr);
|
||||
auto specs = fmt::detail::dynamic_format_specs<>();
|
||||
fmt::detail::parse_format_specs(s, s + N - 1, specs, ctx,
|
||||
fmt::detail::type::float_type);
|
||||
return specs;
|
||||
}
|
||||
|
||||
TEST(core_test, constexpr_parse_format_specs) {
|
||||
using handler = test_format_specs_handler;
|
||||
static_assert(parse_test_specs("<").alignment == fmt::align::left, "");
|
||||
static_assert(parse_test_specs("*^").fill == '*', "");
|
||||
static_assert(parse_test_specs("<").align == fmt::align::left, "");
|
||||
static_assert(parse_test_specs("*^").fill[0] == '*', "");
|
||||
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
|
||||
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
|
||||
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
|
||||
static_assert(parse_test_specs("#").res == handler::hash, "");
|
||||
static_assert(parse_test_specs("0").res == handler::zero, "");
|
||||
static_assert(parse_test_specs("L").res == handler::loc, "");
|
||||
static_assert(parse_test_specs("#").alt, "");
|
||||
static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
|
||||
static_assert(parse_test_specs("L").localized, "");
|
||||
static_assert(parse_test_specs("42").width == 42, "");
|
||||
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
|
||||
static_assert(parse_test_specs(".42").precision == 42, "");
|
||||
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
|
||||
static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, "");
|
||||
static_assert(parse_test_specs("{<").res == handler::error, "");
|
||||
}
|
||||
|
||||
struct test_parse_context {
|
||||
using char_type = char;
|
||||
|
||||
constexpr int next_arg_id() { return 11; }
|
||||
template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
|
||||
FMT_CONSTEXPR void check_dynamic_spec(int) {}
|
||||
|
||||
constexpr const char* begin() { return nullptr; }
|
||||
constexpr const char* end() { return nullptr; }
|
||||
|
||||
void on_error(const char*) {}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
constexpr fmt::detail::dynamic_format_specs<char> parse_dynamic_specs(
|
||||
const char (&s)[N]) {
|
||||
auto specs = fmt::detail::dynamic_format_specs<char>();
|
||||
auto ctx = test_parse_context();
|
||||
auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx);
|
||||
parse_format_specs(s, s + N - 1, h);
|
||||
return specs;
|
||||
}
|
||||
|
||||
TEST(format_test, constexpr_dynamic_specs_handler) {
|
||||
static_assert(parse_dynamic_specs("<").align == fmt::align::left, "");
|
||||
static_assert(parse_dynamic_specs("*^").fill[0] == '*', "");
|
||||
static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, "");
|
||||
static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, "");
|
||||
static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, "");
|
||||
static_assert(parse_dynamic_specs("#").alt, "");
|
||||
static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, "");
|
||||
static_assert(parse_dynamic_specs("42").width == 42, "");
|
||||
static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, "");
|
||||
static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, "");
|
||||
static_assert(parse_dynamic_specs(".42").precision == 42, "");
|
||||
static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, "");
|
||||
static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, "");
|
||||
static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec,
|
||||
"");
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
constexpr test_format_specs_handler check_specs(const char (&s)[N]) {
|
||||
fmt::detail::specs_checker<test_format_specs_handler> checker(
|
||||
test_format_specs_handler(), fmt::detail::type::double_type);
|
||||
parse_format_specs(s, s + N - 1, checker);
|
||||
return checker;
|
||||
}
|
||||
|
||||
TEST(format_test, constexpr_specs_checker) {
|
||||
using handler = test_format_specs_handler;
|
||||
static_assert(check_specs("<").alignment == fmt::align::left, "");
|
||||
static_assert(check_specs("*^").fill == '*', "");
|
||||
static_assert(check_specs("+").sign == fmt::sign::plus, "");
|
||||
static_assert(check_specs("-").sign == fmt::sign::minus, "");
|
||||
static_assert(check_specs(" ").sign == fmt::sign::space, "");
|
||||
static_assert(check_specs("#").res == handler::hash, "");
|
||||
static_assert(check_specs("0").res == handler::zero, "");
|
||||
static_assert(check_specs("42").width == 42, "");
|
||||
static_assert(check_specs("{42}").width_ref.val.index == 42, "");
|
||||
static_assert(check_specs(".42").precision == 42, "");
|
||||
static_assert(check_specs(".{42}").precision_ref.val.index == 42, "");
|
||||
static_assert(check_specs("d").type == fmt::presentation_type::dec, "");
|
||||
static_assert(check_specs("{<").res == handler::error, "");
|
||||
static_assert(
|
||||
parse_test_specs("f").type == fmt::presentation_type::fixed_lower, "");
|
||||
}
|
||||
|
||||
struct test_format_string_handler {
|
||||
constexpr void on_text(const char*, const char*) {}
|
||||
|
||||
constexpr int on_arg_id() { return 0; }
|
||||
constexpr auto on_arg_id() -> int { return 0; }
|
||||
|
||||
template <typename T> constexpr int on_arg_id(T) { return 0; }
|
||||
template <typename T> constexpr auto on_arg_id(T) -> int { return 0; }
|
||||
|
||||
constexpr void on_replacement_field(int, const char*) {}
|
||||
|
||||
constexpr const char* on_format_specs(int, const char* begin, const char*) {
|
||||
constexpr auto on_format_specs(int, const char* begin, const char*) -> const
|
||||
char* {
|
||||
return begin;
|
||||
}
|
||||
|
||||
@ -671,7 +554,7 @@ template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
|
||||
return !h.error;
|
||||
}
|
||||
|
||||
TEST(format_test, constexpr_parse_format_string) {
|
||||
TEST(core_test, constexpr_parse_format_string) {
|
||||
static_assert(parse_string("foo"), "");
|
||||
static_assert(!parse_string("}"), "");
|
||||
static_assert(parse_string("{}"), "");
|
||||
@ -690,19 +573,20 @@ struct disabled_formatter_convertible {
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<enabled_formatter> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) {
|
||||
auto format(enabled_formatter, format_context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct formatter<enabled_ptr_formatter*> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(enabled_ptr_formatter*, format_context& ctx)
|
||||
auto format(enabled_ptr_formatter*, format_context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
@ -723,7 +607,7 @@ struct nonconst_formattable {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<const_formattable> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
@ -735,7 +619,7 @@ template <> struct formatter<const_formattable> {
|
||||
};
|
||||
|
||||
template <> struct formatter<nonconst_formattable> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
@ -757,7 +641,7 @@ struct convertible_to_pointer_formattable {
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<convertible_to_pointer_formattable> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
@ -771,29 +655,17 @@ FMT_END_NAMESPACE
|
||||
|
||||
enum class unformattable_scoped_enum {};
|
||||
|
||||
namespace test {
|
||||
enum class formattable_scoped_enum {};
|
||||
auto format_as(formattable_scoped_enum) -> int { return 42; }
|
||||
|
||||
struct convertible_to_enum {
|
||||
operator formattable_scoped_enum() const { return {}; }
|
||||
};
|
||||
} // namespace test
|
||||
|
||||
TEST(core_test, is_formattable) {
|
||||
#if 0
|
||||
// This should be enabled once corresponding map overloads are gone.
|
||||
static_assert(fmt::is_formattable<signed char*>::value, "");
|
||||
static_assert(fmt::is_formattable<unsigned char*>::value, "");
|
||||
static_assert(fmt::is_formattable<const signed char*>::value, "");
|
||||
static_assert(fmt::is_formattable<const unsigned char*>::value, "");
|
||||
#endif
|
||||
static_assert(!fmt::is_formattable<wchar_t>::value, "");
|
||||
#ifdef __cpp_char8_t
|
||||
static_assert(!fmt::is_formattable<char8_t>::value, "");
|
||||
#endif
|
||||
static_assert(!fmt::is_formattable<char16_t>::value, "");
|
||||
static_assert(!fmt::is_formattable<char32_t>::value, "");
|
||||
static_assert(!fmt::is_formattable<signed char*>::value, "");
|
||||
static_assert(!fmt::is_formattable<unsigned char*>::value, "");
|
||||
static_assert(!fmt::is_formattable<const signed char*>::value, "");
|
||||
static_assert(!fmt::is_formattable<const unsigned char*>::value, "");
|
||||
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
|
||||
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
|
||||
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
|
||||
@ -801,7 +673,8 @@ TEST(core_test, is_formattable) {
|
||||
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
|
||||
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
|
||||
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
|
||||
static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, "");
|
||||
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
|
||||
"");
|
||||
|
||||
static_assert(fmt::is_formattable<const_formattable&>::value, "");
|
||||
static_assert(fmt::is_formattable<const const_formattable&>::value, "");
|
||||
@ -821,45 +694,47 @@ TEST(core_test, is_formattable) {
|
||||
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
|
||||
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
|
||||
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
||||
static_assert(fmt::is_formattable<test::formattable_scoped_enum>::value, "");
|
||||
static_assert(!fmt::is_formattable<test::convertible_to_enum>::value, "");
|
||||
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
||||
}
|
||||
|
||||
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
|
||||
|
||||
TEST(core_test, format_to) {
|
||||
std::string s;
|
||||
auto s = std::string();
|
||||
fmt::format_to(std::back_inserter(s), "{}", 42);
|
||||
EXPECT_EQ(s, "42");
|
||||
}
|
||||
|
||||
TEST(core_test, format_as) {
|
||||
EXPECT_EQ(fmt::format("{}", test::formattable_scoped_enum()), "42");
|
||||
#ifdef __cpp_lib_byte
|
||||
TEST(core_test, format_byte) {
|
||||
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
|
||||
}
|
||||
#endif
|
||||
|
||||
struct convertible_to_int {
|
||||
operator int() const { return 42; }
|
||||
};
|
||||
|
||||
struct convertible_to_c_string {
|
||||
struct convertible_to_cstring {
|
||||
operator const char*() const { return "foo"; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<convertible_to_int> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) {
|
||||
auto format(convertible_to_int, format_context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return std::copy_n("foo", 3, ctx.out());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct formatter<convertible_to_c_string> {
|
||||
template <> struct formatter<convertible_to_cstring> {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(convertible_to_c_string, format_context& ctx)
|
||||
auto format(convertible_to_cstring, format_context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return std::copy_n("bar", 3, ctx.out());
|
||||
}
|
||||
@ -868,7 +743,7 @@ FMT_END_NAMESPACE
|
||||
|
||||
TEST(core_test, formatter_overrides_implicit_conversion) {
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar");
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "bar");
|
||||
}
|
||||
|
||||
// Test that check is not found by ADL.
|
||||
@ -877,27 +752,26 @@ TEST(core_test, adl_check) {
|
||||
EXPECT_EQ(fmt::format("{}", test_struct()), "test");
|
||||
}
|
||||
|
||||
TEST(core_test, to_string_view_foreign_strings) {
|
||||
using namespace test_ns;
|
||||
EXPECT_EQ(to_string_view(test_string<char>("42")), "42");
|
||||
fmt::detail::type type =
|
||||
fmt::detail::mapped_type_constant<test_string<char>,
|
||||
fmt::format_context>::value;
|
||||
EXPECT_EQ(type, fmt::detail::type::string_type);
|
||||
}
|
||||
|
||||
struct implicitly_convertible_to_string {
|
||||
operator std::string() const { return "foo"; }
|
||||
};
|
||||
|
||||
struct implicitly_convertible_to_string_view {
|
||||
operator fmt::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(core_test, format_implicitly_convertible_to_string_view) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
|
||||
TEST(core_test, no_implicit_conversion_to_string_view) {
|
||||
EXPECT_FALSE(
|
||||
fmt::is_formattable<implicitly_convertible_to_string_view>::value);
|
||||
}
|
||||
|
||||
#ifdef FMT_USE_STRING_VIEW
|
||||
struct implicitly_convertible_to_std_string_view {
|
||||
operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(core_test, no_implicit_conversion_to_std_string_view) {
|
||||
EXPECT_FALSE(
|
||||
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
|
||||
}
|
||||
#endif
|
||||
|
||||
// std::is_constructible is broken in MSVC until version 2015.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
|
||||
struct explicitly_convertible_to_string_view {
|
||||
@ -926,25 +800,6 @@ TEST(core_test, format_explicitly_convertible_to_std_string_view) {
|
||||
# endif
|
||||
#endif
|
||||
|
||||
struct convertible_to_long_long {
|
||||
operator long long() const { return 1LL << 32; }
|
||||
};
|
||||
|
||||
TEST(format_test, format_convertible_to_long_long) {
|
||||
EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long()));
|
||||
}
|
||||
|
||||
struct disabled_rvalue_conversion {
|
||||
operator const char*() const& { return "foo"; }
|
||||
operator const char*() & { return "foo"; }
|
||||
operator const char*() const&& = delete;
|
||||
operator const char*() && = delete;
|
||||
};
|
||||
|
||||
TEST(core_test, disabled_rvalue_conversion) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
|
||||
}
|
||||
|
||||
namespace adl_test {
|
||||
template <typename... T> void make_format_args(const T&...) = delete;
|
||||
|
||||
@ -976,3 +831,28 @@ TEST(core_test, has_const_formatter) {
|
||||
TEST(core_test, format_nonconst) {
|
||||
EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test");
|
||||
}
|
||||
|
||||
struct its_a_trap {
|
||||
template <typename T> operator T() const {
|
||||
auto v = T();
|
||||
v.x = 42;
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<its_a_trap> {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto format(its_a_trap, format_context& ctx) const -> decltype(ctx.out()) {
|
||||
auto s = string_view("42");
|
||||
return std::copy(s.begin(), s.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(core_test, trappy_conversion) {
|
||||
EXPECT_EQ(fmt::format("{}", its_a_trap()), "42");
|
||||
}
|
||||
|
@ -8,12 +8,14 @@
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/ostream.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "fmt/xchar.h"
|
||||
#undef I
|
||||
|
||||
// Exercise the API to verify that everything we expect to can compile.
|
||||
void test_format_api() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
cmake_minimum_required(VERSION 3.8...3.25)
|
||||
|
||||
project(fmt-test)
|
||||
|
||||
|
@ -196,43 +196,6 @@ TEST(fp_test, multiply) {
|
||||
EXPECT_EQ(v.e, 4 + 8 + 64);
|
||||
}
|
||||
|
||||
TEST(fp_test, get_cached_power) {
|
||||
using limits = std::numeric_limits<double>;
|
||||
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
|
||||
int dec_exp = 0;
|
||||
auto power = fmt::detail::get_cached_power(exp, dec_exp);
|
||||
bigint exact, cache(power.f);
|
||||
if (dec_exp >= 0) {
|
||||
exact.assign_pow10(dec_exp);
|
||||
if (power.e <= 0)
|
||||
exact <<= -power.e;
|
||||
else
|
||||
cache <<= power.e;
|
||||
exact.align(cache);
|
||||
cache.align(exact);
|
||||
auto exact_str = fmt::to_string(exact);
|
||||
auto cache_str = fmt::to_string(cache);
|
||||
EXPECT_EQ(exact_str.size(), cache_str.size());
|
||||
EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
|
||||
int diff = cache_str[15] - exact_str[15];
|
||||
if (diff == 1)
|
||||
EXPECT_GT(exact_str[16], '8');
|
||||
else
|
||||
EXPECT_EQ(diff, 0);
|
||||
} else {
|
||||
cache.assign_pow10(-dec_exp);
|
||||
cache *= power.f + 1; // Inexact check.
|
||||
exact = 1;
|
||||
exact <<= -power.e;
|
||||
exact.align(cache);
|
||||
auto exact_str = fmt::to_string(exact);
|
||||
auto cache_str = fmt::to_string(cache);
|
||||
EXPECT_EQ(exact_str.size(), cache_str.size());
|
||||
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(fp_test, dragonbox_max_k) {
|
||||
using fmt::detail::dragonbox::floor_log10_pow2;
|
||||
using float_info = fmt::detail::dragonbox::float_info<float>;
|
||||
@ -242,66 +205,18 @@ TEST(fp_test, dragonbox_max_k) {
|
||||
floor_log10_pow2(std::numeric_limits<float>::min_exponent -
|
||||
fmt::detail::num_significand_bits<float>() - 1));
|
||||
using double_info = fmt::detail::dragonbox::float_info<double>;
|
||||
EXPECT_EQ(
|
||||
fmt::detail::const_check(double_info::max_k),
|
||||
double_info::kappa -
|
||||
floor_log10_pow2(std::numeric_limits<double>::min_exponent -
|
||||
fmt::detail::num_significand_bits<double>() - 1));
|
||||
}
|
||||
|
||||
TEST(fp_test, get_round_direction) {
|
||||
using fmt::detail::get_round_direction;
|
||||
using fmt::detail::round_direction;
|
||||
EXPECT_EQ(get_round_direction(100, 50, 0), round_direction::down);
|
||||
EXPECT_EQ(get_round_direction(100, 51, 0), round_direction::up);
|
||||
EXPECT_EQ(get_round_direction(100, 40, 10), round_direction::down);
|
||||
EXPECT_EQ(get_round_direction(100, 60, 10), round_direction::up);
|
||||
for (size_t i = 41; i < 60; ++i)
|
||||
EXPECT_EQ(get_round_direction(100, i, 10), round_direction::unknown);
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
|
||||
// Check that remainder + error doesn't overflow.
|
||||
EXPECT_EQ(get_round_direction(max, max - 1, 2), round_direction::up);
|
||||
// Check that 2 * (remainder + error) doesn't overflow.
|
||||
EXPECT_EQ(get_round_direction(max, max / 2 + 1, max / 2),
|
||||
round_direction::unknown);
|
||||
// Check that remainder - error doesn't overflow.
|
||||
EXPECT_EQ(get_round_direction(100, 40, 41), round_direction::unknown);
|
||||
// Check that 2 * (remainder - error) doesn't overflow.
|
||||
EXPECT_EQ(get_round_direction(max, max - 1, 1), round_direction::up);
|
||||
}
|
||||
|
||||
TEST(fp_test, fixed_handler) {
|
||||
struct handler : fmt::detail::gen_digits_handler {
|
||||
char buffer[10];
|
||||
handler(int prec = 0) : fmt::detail::gen_digits_handler() {
|
||||
buf = buffer;
|
||||
precision = prec;
|
||||
}
|
||||
};
|
||||
handler().on_digit('0', 100, 99, 0, false);
|
||||
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure);
|
||||
namespace digits = fmt::detail::digits;
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error);
|
||||
// Check that divisor - error doesn't overflow.
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error);
|
||||
// Check that 2 * error doesn't overflow.
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error);
|
||||
}
|
||||
|
||||
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
|
||||
auto buf = fmt::memory_buffer();
|
||||
format_float(0.42, -1, fmt::detail::float_specs(), buf);
|
||||
EXPECT_EQ(fmt::detail::const_check(double_info::max_k),
|
||||
double_info::kappa -
|
||||
floor_log10_pow2(
|
||||
std::numeric_limits<double>::min_exponent -
|
||||
2 * fmt::detail::num_significand_bits<double>() - 1));
|
||||
}
|
||||
|
||||
TEST(format_impl_test, format_error_code) {
|
||||
std::string msg = "error 42", sep = ": ";
|
||||
{
|
||||
auto buffer = fmt::memory_buffer();
|
||||
format_to(fmt::appender(buffer), "garbage");
|
||||
fmt::format_to(fmt::appender(buffer), "garbage");
|
||||
fmt::detail::format_error_code(buffer, 42, "test");
|
||||
EXPECT_EQ(to_string(buffer), "test: " + msg);
|
||||
}
|
||||
@ -353,6 +268,16 @@ TEST(format_impl_test, count_digits) {
|
||||
test_count_digits<uint64_t>();
|
||||
}
|
||||
|
||||
TEST(format_impl_test, countl_zero) {
|
||||
constexpr auto num_bits = fmt::detail::num_bits<uint32_t>();
|
||||
uint32_t n = 1u;
|
||||
for (int i = 1; i < num_bits - 1; i++) {
|
||||
n <<= 1;
|
||||
EXPECT_EQ(fmt::detail::countl_zero(n - 1), num_bits - i);
|
||||
EXPECT_EQ(fmt::detail::countl_zero(n), num_bits - i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
#if FMT_USE_FLOAT128
|
||||
TEST(format_impl_test, write_float128) {
|
||||
auto s = std::string();
|
||||
@ -372,6 +297,8 @@ struct double_double {
|
||||
auto operator-() const -> double_double { return double_double(-a, -b); }
|
||||
};
|
||||
|
||||
auto format_as(double_double d) -> double { return d; }
|
||||
|
||||
bool operator>=(const double_double& lhs, const double_double& rhs) {
|
||||
return lhs.a + lhs.b >= rhs.a + rhs.b;
|
||||
}
|
||||
@ -384,6 +311,8 @@ struct slow_float {
|
||||
auto operator-() const -> slow_float { return slow_float(-value); }
|
||||
};
|
||||
|
||||
auto format_as(slow_float f) -> float { return f; }
|
||||
|
||||
namespace std {
|
||||
template <> struct is_floating_point<double_double> : std::true_type {};
|
||||
template <> struct numeric_limits<double_double> {
|
||||
@ -545,3 +474,10 @@ TEST(format_impl_test, utf8_decode_bogus_byte_sequences) {
|
||||
EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c
|
||||
EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len);
|
||||
}
|
||||
|
||||
TEST(format_impl_test, to_utf8) {
|
||||
auto s = std::string("ёжик");
|
||||
auto u = fmt::detail::to_utf8<wchar_t>(L"\x0451\x0436\x0438\x043A");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
@ -646,7 +646,7 @@ TEST(format_test, fill) {
|
||||
fmt::format(string_view("{:\0>4}", 6), '*'));
|
||||
EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0),
|
||||
format_error, "invalid type specifier");
|
||||
format_error, "invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, plus_sign) {
|
||||
@ -654,27 +654,25 @@ TEST(format_test, plus_sign) {
|
||||
EXPECT_EQ("-42", fmt::format("{0:+}", -42));
|
||||
EXPECT_EQ("+42", fmt::format("{0:+}", 42));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("+42", fmt::format("{0:+}", 42l));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("+42", fmt::format("{0:+}", 42ll));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("+42", fmt::format("{0:+}", __int128_t(42)));
|
||||
#endif
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0));
|
||||
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+"), 'c'), format_error,
|
||||
"missing '}' in format string");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 'c'), format_error,
|
||||
"invalid format specifier for char");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0:+}"), reinterpret_cast<void*>(0x42)),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, minus_sign) {
|
||||
@ -682,24 +680,22 @@ TEST(format_test, minus_sign) {
|
||||
EXPECT_EQ("-42", fmt::format("{0:-}", -42));
|
||||
EXPECT_EQ("42", fmt::format("{0:-}", 42));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("42", fmt::format("{0:-}", 42l));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("42", fmt::format("{0:-}", 42ll));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("42", fmt::format("{0:-}", 42.0));
|
||||
EXPECT_EQ("42", fmt::format("{0:-}", 42.0l));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-"), 'c'), format_error,
|
||||
"missing '}' in format string");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 'c'), format_error,
|
||||
"invalid format specifier for char");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0:-}"), reinterpret_cast<void*>(0x42)),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, space_sign) {
|
||||
@ -707,24 +703,22 @@ TEST(format_test, space_sign) {
|
||||
EXPECT_EQ("-42", fmt::format("{0: }", -42));
|
||||
EXPECT_EQ(" 42", fmt::format("{0: }", 42));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ(" 42", fmt::format("{0: }", 42l));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ(" 42", fmt::format("{0: }", 42ll));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error,
|
||||
"format specifier requires signed argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0));
|
||||
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: "), 'c'), format_error,
|
||||
"missing '}' in format string");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 'c'), format_error,
|
||||
"invalid format specifier for char");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0: }"), reinterpret_cast<void*>(0x42)),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, hash_flag) {
|
||||
@ -761,8 +755,8 @@ TEST(format_test, hash_flag) {
|
||||
EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull));
|
||||
EXPECT_EQ("042", fmt::format("{0:#o}", 042ull));
|
||||
|
||||
EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0));
|
||||
EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0l));
|
||||
EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0));
|
||||
EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0l));
|
||||
EXPECT_EQ("4.e+01", fmt::format("{:#.0e}", 42.0));
|
||||
EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01));
|
||||
EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5));
|
||||
@ -772,10 +766,10 @@ TEST(format_test, hash_flag) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
|
||||
"invalid format specifier for char");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0:#}"), reinterpret_cast<void*>(0x42)),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, zero_flag) {
|
||||
@ -799,6 +793,17 @@ TEST(format_test, zero_flag) {
|
||||
format_error, "format specifier requires numeric argument");
|
||||
}
|
||||
|
||||
TEST(format_test, zero_flag_and_align) {
|
||||
// If the 0 character and an align option both appear, the 0 character is
|
||||
// ignored.
|
||||
EXPECT_EQ("42 ", fmt::format("{0:<05}", 42));
|
||||
EXPECT_EQ("-42 ", fmt::format("{0:<05}", -42));
|
||||
EXPECT_EQ(" 42 ", fmt::format("{0:^05}", 42));
|
||||
EXPECT_EQ(" -42 ", fmt::format("{0:^05}", -42));
|
||||
EXPECT_EQ(" 42", fmt::format("{0:>05}", 42));
|
||||
EXPECT_EQ(" -42", fmt::format("{0:>05}", -42));
|
||||
}
|
||||
|
||||
TEST(format_test, width) {
|
||||
char format_str[buffer_size];
|
||||
safe_sprintf(format_str, "{0:%u", UINT_MAX);
|
||||
@ -831,9 +836,9 @@ TEST(format_test, width) {
|
||||
EXPECT_EQ("str ", fmt::format("{0:12}", "str"));
|
||||
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
|
||||
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
|
||||
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.0");
|
||||
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
|
||||
EXPECT_EQ(fmt::format("{:6c}", static_cast<int>('x')), "x ");
|
||||
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), "000000");
|
||||
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
|
||||
}
|
||||
|
||||
TEST(format_test, runtime_width) {
|
||||
@ -902,54 +907,54 @@ TEST(format_test, precision) {
|
||||
char format_str[buffer_size];
|
||||
safe_sprintf(format_str, "{0:.%u", UINT_MAX);
|
||||
increment(format_str + 4);
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"number is too big");
|
||||
size_t size = std::strlen(format_str);
|
||||
format_str[size] = '}';
|
||||
format_str[size + 1] = 0;
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"number is too big");
|
||||
|
||||
safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u);
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"number is too big");
|
||||
safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u);
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"number is too big");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0), format_error,
|
||||
"missing precision specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0), format_error,
|
||||
"missing precision specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error,
|
||||
"invalid precision");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error,
|
||||
"invalid precision");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42u), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42u), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42l), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42l), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ul), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ul), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ll), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ll), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ull), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ull), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345));
|
||||
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l));
|
||||
EXPECT_EQ("1.2e+56", fmt::format("{:.2}", 1.234e56));
|
||||
@ -1028,10 +1033,10 @@ TEST(format_test, precision) {
|
||||
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)),
|
||||
format_error, "precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
|
||||
format_error, "precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
|
||||
fmt::detail::max_value<int>()),
|
||||
format_error, "number is too big");
|
||||
@ -1047,97 +1052,86 @@ TEST(format_test, runtime_precision) {
|
||||
char format_str[buffer_size];
|
||||
safe_sprintf(format_str, "{0:.{%u", UINT_MAX);
|
||||
increment(format_str + 5);
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"invalid format string");
|
||||
size_t size = std::strlen(format_str);
|
||||
format_str[size] = '}';
|
||||
format_str[size + 1] = 0;
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"argument not found");
|
||||
format_str[size + 1] = '}';
|
||||
format_str[size + 2] = 0;
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error,
|
||||
"argument not found");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0.0), format_error,
|
||||
"invalid format string");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0.0), format_error,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0.0), format_error,
|
||||
"invalid format string");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}"), 0, 0), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0), format_error,
|
||||
"argument not found");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0), format_error,
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0.0), format_error,
|
||||
"invalid format string");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1), format_error,
|
||||
"negative precision");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)),
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1),
|
||||
format_error, "negative precision");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)),
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error,
|
||||
"negative precision");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l),
|
||||
format_error, "negative precision");
|
||||
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
|
||||
long value = INT_MAX;
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (value + 1)),
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)),
|
||||
format_error, "number is too big");
|
||||
}
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)),
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)),
|
||||
format_error, "number is too big");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error,
|
||||
"precision is not integer");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error,
|
||||
"precision is not integer");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'),
|
||||
format_error, "precision is not integer");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0),
|
||||
format_error, "precision is not integer");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42u, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42l, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ul, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ul, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ll, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ll, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ull, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ull, 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2));
|
||||
EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l));
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"),
|
||||
reinterpret_cast<void*>(0xcafe), 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"),
|
||||
reinterpret_cast<void*>(0xcafe), 2),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
format_error, "invalid format specifier");
|
||||
|
||||
EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2));
|
||||
}
|
||||
@ -1167,7 +1161,7 @@ void check_unknown_types(const T& value, const char* types, const char*) {
|
||||
char c = static_cast<char>(i);
|
||||
if (std::strchr(types, c) || std::strchr(special, c) || !c) continue;
|
||||
safe_sprintf(format_str, "{0:10%c}", c);
|
||||
const char* message = "invalid type specifier";
|
||||
const char* message = "invalid format specifier";
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), value),
|
||||
format_error, message)
|
||||
<< format_str << " " << message;
|
||||
@ -1176,7 +1170,7 @@ void check_unknown_types(const T& value, const char* types, const char*) {
|
||||
|
||||
TEST(format_test, format_int) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error,
|
||||
"invalid type specifier");
|
||||
"invalid format specifier");
|
||||
check_unknown_types(42, "bBdoxXnLc", "integer");
|
||||
EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x')));
|
||||
}
|
||||
@ -1205,6 +1199,7 @@ constexpr auto uint128_max = ~static_cast<__uint128_t>(0);
|
||||
TEST(format_test, format_dec) {
|
||||
EXPECT_EQ("0", fmt::format("{0}", 0));
|
||||
EXPECT_EQ("42", fmt::format("{0}", 42));
|
||||
EXPECT_EQ("42>", fmt::format("{:}>", 42));
|
||||
EXPECT_EQ("42", fmt::format("{0:d}", 42));
|
||||
EXPECT_EQ("42", fmt::format("{0}", 42u));
|
||||
EXPECT_EQ("-42", fmt::format("{0}", -42));
|
||||
@ -1343,16 +1338,59 @@ TEST(format_test, format_double) {
|
||||
EXPECT_EQ(fmt::format("{:f}", 392.65), "392.650000");
|
||||
EXPECT_EQ(fmt::format("{:F}", 392.65), "392.650000");
|
||||
EXPECT_EQ(fmt::format("{:L}", 42.0), "42");
|
||||
EXPECT_EQ(fmt::format("{:24a}", 4.2f), " 0x1.0cccccp+2");
|
||||
EXPECT_EQ(fmt::format("{:24a}", 4.2), " 0x1.0cccccccccccdp+2");
|
||||
EXPECT_EQ(fmt::format("{:<24a}", 4.2), "0x1.0cccccccccccdp+2 ");
|
||||
EXPECT_EQ(fmt::format("{0:e}", 392.65), "3.926500e+02");
|
||||
EXPECT_EQ(fmt::format("{0:E}", 392.65), "3.926500E+02");
|
||||
EXPECT_EQ(fmt::format("{0:+010.4g}", 392.65), "+0000392.6");
|
||||
char buffer[buffer_size];
|
||||
safe_sprintf(buffer, "%a", -42.0);
|
||||
EXPECT_EQ(fmt::format("{:a}", -42.0), buffer);
|
||||
safe_sprintf(buffer, "%A", -42.0);
|
||||
EXPECT_EQ(fmt::format("{:A}", -42.0), buffer);
|
||||
|
||||
#if FMT_CPLUSPLUS >= 201703L
|
||||
double xd = 0x1.ffffffffffp+2;
|
||||
safe_sprintf(buffer, "%.*a", 10, xd);
|
||||
EXPECT_EQ(fmt::format("{:.10a}", xd), buffer);
|
||||
safe_sprintf(buffer, "%.*a", 9, xd);
|
||||
EXPECT_EQ(fmt::format("{:.9a}", xd), buffer);
|
||||
|
||||
if (std::numeric_limits<long double>::digits == 64) {
|
||||
auto ld = 0xf.ffffffffffp-3l;
|
||||
EXPECT_EQ(fmt::format("{:a}", ld), "0xf.ffffffffffp-3");
|
||||
EXPECT_EQ(fmt::format("{:.10a}", ld), "0xf.ffffffffffp-3");
|
||||
EXPECT_EQ(fmt::format("{:.9a}", ld), "0x1.000000000p+1");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fmt::detail::const_check(std::numeric_limits<double>::is_iec559)) {
|
||||
double d = (std::numeric_limits<double>::min)();
|
||||
EXPECT_EQ(fmt::format("{:a}", d), "0x1p-1022");
|
||||
EXPECT_EQ(fmt::format("{:#a}", d), "0x1.p-1022");
|
||||
|
||||
d = (std::numeric_limits<double>::max)();
|
||||
safe_sprintf(buffer, "%a", d);
|
||||
EXPECT_EQ(fmt::format("{:a}", d), buffer);
|
||||
|
||||
d = std::numeric_limits<double>::denorm_min();
|
||||
EXPECT_EQ(fmt::format("{:a}", d), "0x0.0000000000001p-1022");
|
||||
}
|
||||
|
||||
if (std::numeric_limits<long double>::digits == 64) {
|
||||
auto ld = (std::numeric_limits<long double>::min)();
|
||||
EXPECT_EQ(fmt::format("{:a}", ld), "0x8p-16385");
|
||||
|
||||
ld = (std::numeric_limits<long double>::max)();
|
||||
EXPECT_EQ(fmt::format("{:a}", ld), "0xf.fffffffffffffffp+16380");
|
||||
|
||||
ld = std::numeric_limits<long double>::denorm_min();
|
||||
EXPECT_EQ(fmt::format("{:a}", ld), "0x0.000000000000001p-16382");
|
||||
}
|
||||
|
||||
safe_sprintf(buffer, "%.*a", 10, 4.2);
|
||||
EXPECT_EQ(fmt::format("{:.10a}", 4.2), buffer);
|
||||
|
||||
EXPECT_EQ(fmt::format("{:a}", -42.0), "-0x1.5p+5");
|
||||
EXPECT_EQ(fmt::format("{:A}", -42.0), "-0X1.5P+5");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:f}", 9223372036854775807.0),
|
||||
"9223372036854775808.000000");
|
||||
}
|
||||
@ -1375,9 +1413,6 @@ TEST(format_test, precision_rounding) {
|
||||
EXPECT_EQ("1.9156918820264798e-56",
|
||||
fmt::format("{}", 1.9156918820264798e-56));
|
||||
EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15));
|
||||
|
||||
// Trigger a rounding error in Grisu by a specially chosen number.
|
||||
EXPECT_EQ("3788512123356.985352", fmt::format("{:f}", 3788512123356.985352));
|
||||
}
|
||||
|
||||
TEST(format_test, prettify_float) {
|
||||
@ -1392,7 +1427,6 @@ TEST(format_test, prettify_float) {
|
||||
EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
|
||||
EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
|
||||
EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
|
||||
EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
|
||||
EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f));
|
||||
}
|
||||
|
||||
@ -1437,6 +1471,8 @@ TEST(format_test, format_infinity) {
|
||||
TEST(format_test, format_long_double) {
|
||||
EXPECT_EQ("0", fmt::format("{0:}", 0.0l));
|
||||
EXPECT_EQ("0.000000", fmt::format("{0:f}", 0.0l));
|
||||
EXPECT_EQ("0.0", fmt::format("{:.1f}", 0.000000001l));
|
||||
EXPECT_EQ("0.10", fmt::format("{:.2f}", 0.099l));
|
||||
EXPECT_EQ("392.65", fmt::format("{0:}", 392.65l));
|
||||
EXPECT_EQ("392.65", fmt::format("{0:g}", 392.65l));
|
||||
EXPECT_EQ("392.65", fmt::format("{0:G}", 392.65l));
|
||||
@ -1446,8 +1482,14 @@ TEST(format_test, format_long_double) {
|
||||
safe_sprintf(buffer, "%Le", 392.65l);
|
||||
EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65l));
|
||||
EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.64l));
|
||||
safe_sprintf(buffer, "%La", 3.31l);
|
||||
EXPECT_EQ(buffer, fmt::format("{:a}", 3.31l));
|
||||
|
||||
auto ld = 3.31l;
|
||||
if (fmt::detail::is_double_double<decltype(ld)>::value) {
|
||||
safe_sprintf(buffer, "%a", static_cast<double>(ld));
|
||||
EXPECT_EQ(buffer, fmt::format("{:a}", ld));
|
||||
} else if (std::numeric_limits<long double>::digits == 64) {
|
||||
EXPECT_EQ(fmt::format("{:a}", ld), "0xd.3d70a3d70a3d70ap-2");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(format_test, format_char) {
|
||||
@ -1466,6 +1508,7 @@ TEST(format_test, format_char) {
|
||||
|
||||
EXPECT_EQ("\n", fmt::format("{}", '\n'));
|
||||
EXPECT_EQ("'\\n'", fmt::format("{:?}", '\n'));
|
||||
EXPECT_EQ("ff", fmt::format("{:x}", '\xff'));
|
||||
}
|
||||
|
||||
TEST(format_test, format_volatile_char) {
|
||||
@ -1507,6 +1550,12 @@ TEST(format_test, format_pointer) {
|
||||
std::unique_ptr<int> up(new int(1));
|
||||
EXPECT_EQ(fmt::format("{}", fmt::ptr(up.get())),
|
||||
fmt::format("{}", fmt::ptr(up)));
|
||||
struct custom_deleter {
|
||||
void operator()(int* p) const { delete p; }
|
||||
};
|
||||
std::unique_ptr<int, custom_deleter> upcd(new int(1));
|
||||
EXPECT_EQ(fmt::format("{}", fmt::ptr(upcd.get())),
|
||||
fmt::format("{}", fmt::ptr(upcd)));
|
||||
std::shared_ptr<int> sp(new int(1));
|
||||
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
|
||||
fmt::format("{}", fmt::ptr(sp)));
|
||||
@ -1581,7 +1630,7 @@ struct fmt::formatter<explicitly_convertible_to_std_string_view>
|
||||
: formatter<std::string_view> {
|
||||
auto format(explicitly_convertible_to_std_string_view v, format_context& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
return format_to(ctx.out(), "'{}'", std::string_view(v));
|
||||
return fmt::format_to(ctx.out(), "'{}'", std::string_view(v));
|
||||
}
|
||||
};
|
||||
|
||||
@ -1591,27 +1640,6 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
|
||||
}
|
||||
#endif
|
||||
|
||||
struct converible_to_anything {
|
||||
template <typename T> operator T() const { return T(); }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<converible_to_anything> {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto format(converible_to_anything, format_context& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
return format_to(ctx.out(), "foo");
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(format_test, format_convertible_to_anything) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", converible_to_anything()));
|
||||
}
|
||||
|
||||
class Answer {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
@ -1624,7 +1652,8 @@ template <> struct formatter<date> {
|
||||
}
|
||||
|
||||
auto format(const date& d, format_context& ctx) -> decltype(ctx.out()) {
|
||||
format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day());
|
||||
// Namespace-qualify to avoid ambiguity with std::format_to.
|
||||
fmt::format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day());
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
@ -1646,8 +1675,7 @@ TEST(format_test, format_custom) {
|
||||
|
||||
TEST(format_test, format_to_custom) {
|
||||
char buf[10] = {};
|
||||
auto end =
|
||||
&*fmt::format_to(fmt::detail::make_checked(buf, 10), "{}", Answer());
|
||||
auto end = fmt::format_to(buf, "{}", Answer());
|
||||
EXPECT_EQ(end, buf + 2);
|
||||
EXPECT_STREQ(buf, "42");
|
||||
}
|
||||
@ -1665,7 +1693,7 @@ TEST(format_test, format_examples) {
|
||||
EXPECT_EQ("42", fmt::format("{}", 42));
|
||||
|
||||
memory_buffer out;
|
||||
format_to(std::back_inserter(out), "The answer is {}.", 42);
|
||||
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
|
||||
EXPECT_EQ("The answer is 42.", to_string(out));
|
||||
|
||||
const char* filename = "nonexistent";
|
||||
@ -1717,7 +1745,7 @@ TEST(format_test, format_examples) {
|
||||
EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42));
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("The answer is {:d}"), "forty-two"),
|
||||
format_error, "invalid type specifier");
|
||||
format_error, "invalid format specifier");
|
||||
|
||||
EXPECT_WRITE(
|
||||
stdout, fmt::print("{}", std::numeric_limits<double>::infinity()), "inf");
|
||||
@ -1727,26 +1755,15 @@ TEST(format_test, print) {
|
||||
EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!");
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
|
||||
"Don't panic!");
|
||||
EXPECT_WRITE(stdout, fmt::println("Don't {}!", "panic"), "Don't panic!\n");
|
||||
EXPECT_WRITE(stderr, fmt::println(stderr, "Don't {}!", "panic"),
|
||||
"Don't panic!\n");
|
||||
}
|
||||
|
||||
TEST(format_test, variadic) {
|
||||
EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1));
|
||||
}
|
||||
|
||||
TEST(format_test, dynamic) {
|
||||
using ctx = fmt::format_context;
|
||||
auto args = std::vector<fmt::basic_format_arg<ctx>>();
|
||||
args.emplace_back(fmt::detail::make_arg<ctx>(42));
|
||||
args.emplace_back(fmt::detail::make_arg<ctx>("abc1"));
|
||||
args.emplace_back(fmt::detail::make_arg<ctx>(1.5f));
|
||||
|
||||
std::string result = fmt::vformat(
|
||||
"{} and {} and {}",
|
||||
fmt::format_args(args.data(), static_cast<int>(args.size())));
|
||||
|
||||
EXPECT_EQ("42 and abc1 and 1.5", result);
|
||||
}
|
||||
|
||||
TEST(format_test, bytes) {
|
||||
auto s = fmt::format("{:10}", fmt::bytes("ёжик"));
|
||||
EXPECT_EQ("ёжик ", s);
|
||||
@ -1789,12 +1806,6 @@ TEST(format_test, join) {
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_byte
|
||||
TEST(format_test, format_byte) {
|
||||
using arg_mapper = fmt::detail::arg_mapper<fmt::format_context>;
|
||||
EXPECT_EQ(arg_mapper().map(std::byte(42)), 42);
|
||||
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
|
||||
}
|
||||
|
||||
TEST(format_test, join_bytes) {
|
||||
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
|
||||
EXPECT_EQ(fmt::format("{}", fmt::join(v, ", ")), "1, 2, 3");
|
||||
@ -1803,7 +1814,7 @@ TEST(format_test, join_bytes) {
|
||||
|
||||
std::string vformat_message(int id, const char* format, fmt::format_args args) {
|
||||
auto buffer = fmt::memory_buffer();
|
||||
format_to(fmt::appender(buffer), "[{}] ", id);
|
||||
fmt::format_to(fmt::appender(buffer), "[{}] ", id);
|
||||
vformat_to(fmt::appender(buffer), format, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
@ -1832,9 +1843,6 @@ TEST(format_test, unpacked_args) {
|
||||
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
|
||||
}
|
||||
|
||||
struct string_like {};
|
||||
fmt::string_view to_string_view(string_like) { return "foo"; }
|
||||
|
||||
constexpr char with_null[3] = {'{', '}', '\0'};
|
||||
constexpr char no_null[2] = {'{', '}'};
|
||||
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
|
||||
@ -1843,7 +1851,6 @@ static constexpr const char static_no_null[2] = {'{', '}'};
|
||||
TEST(format_test, compile_time_string) {
|
||||
EXPECT_EQ("foo", fmt::format(FMT_STRING("foo")));
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
using namespace fmt::literals;
|
||||
@ -1852,6 +1859,7 @@ TEST(format_test, compile_time_string) {
|
||||
EXPECT_EQ("", fmt::format(FMT_STRING("")));
|
||||
EXPECT_EQ("", fmt::format(FMT_STRING(""), "arg"_a = 42));
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{answer}"), "answer"_a = Answer()));
|
||||
EXPECT_EQ("1 2", fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2));
|
||||
#endif
|
||||
|
||||
(void)static_with_null;
|
||||
@ -1917,46 +1925,6 @@ TEST(format_test, non_null_terminated_format_string) {
|
||||
EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42));
|
||||
}
|
||||
|
||||
struct variant {
|
||||
enum { int_type, string_type } type;
|
||||
explicit variant(int) : type(int_type) {}
|
||||
explicit variant(const char*) : type(string_type) {}
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<variant> : dynamic_formatter<> {
|
||||
auto format(variant value, format_context& ctx) -> decltype(ctx.out()) {
|
||||
if (value.type == variant::int_type)
|
||||
return dynamic_formatter<>::format(42, ctx);
|
||||
return dynamic_formatter<>::format("foo", ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(format_test, dynamic_formatter) {
|
||||
auto num = variant(42);
|
||||
auto str = variant("foo");
|
||||
EXPECT_EQ("42", fmt::format("{:d}", num));
|
||||
EXPECT_EQ("foo", fmt::format("{:s}", str));
|
||||
EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}}"), num), format_error,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:{0}}"), num), format_error,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:+}"), str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:-}"), str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{: }"), str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:#}"), str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:0}"), str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2}"), num), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
}
|
||||
|
||||
namespace adl_test {
|
||||
namespace fmt {
|
||||
namespace detail {
|
||||
@ -1969,30 +1937,26 @@ template <typename, typename OutputIt> void write(OutputIt, foo) = delete;
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <>
|
||||
struct formatter<adl_test::fmt::detail::foo> : formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(adl_test::fmt::detail::foo, FormatContext& ctx)
|
||||
auto format(adl_test::fmt::detail::foo, format_context& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<std::string>::format("foo", ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
struct convertible_to_int {
|
||||
operator int() const { return value; }
|
||||
|
||||
int value = 42;
|
||||
};
|
||||
|
||||
TEST(format_test, to_string) {
|
||||
EXPECT_EQ(fmt::to_string(42), "42");
|
||||
EXPECT_EQ(fmt::to_string(reinterpret_cast<void*>(0x1234)), "0x1234");
|
||||
EXPECT_EQ(fmt::to_string(adl_test::fmt::detail::foo()), "foo");
|
||||
EXPECT_EQ(fmt::to_string(convertible_to_int()), "42");
|
||||
EXPECT_EQ(fmt::to_string(foo), "0");
|
||||
|
||||
#if FMT_USE_FLOAT128
|
||||
EXPECT_EQ(fmt::to_string(__float128(0.5)), "0.5");
|
||||
#endif
|
||||
|
||||
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
||||
EXPECT_EQ(fmt::to_string(std::string_view()), "");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(format_test, output_iterators) {
|
||||
@ -2006,6 +1970,7 @@ TEST(format_test, output_iterators) {
|
||||
|
||||
TEST(format_test, formatted_size) {
|
||||
EXPECT_EQ(2u, fmt::formatted_size("{}", 42));
|
||||
EXPECT_EQ(2u, fmt::formatted_size(std::locale(), "{}", 42));
|
||||
}
|
||||
|
||||
TEST(format_test, format_to_no_args) {
|
||||
@ -2108,120 +2073,10 @@ TEST(format_test, format_to_n_output_iterator) {
|
||||
EXPECT_STREQ(buf, "42");
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
struct test_error_handler {
|
||||
const char*& error;
|
||||
|
||||
FMT_CONSTEXPR test_error_handler(const char*& err) : error(err) {}
|
||||
|
||||
FMT_CONSTEXPR test_error_handler(const test_error_handler& other)
|
||||
: error(other.error) {}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
if (!error) error = message;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR size_t len(const char* s) {
|
||||
size_t len = 0;
|
||||
while (*s++) ++len;
|
||||
return len;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool equal(const char* s1, const char* s2) {
|
||||
if (!s1 || !s2) return s1 == s2;
|
||||
while (*s1 && *s1 == *s2) {
|
||||
++s1;
|
||||
++s2;
|
||||
}
|
||||
return *s1 == *s2;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
|
||||
const char* actual_error = nullptr;
|
||||
auto s = string_view(fmt, len(fmt));
|
||||
auto checker =
|
||||
fmt::detail::format_string_checker<char, test_error_handler, Args...>(
|
||||
s, test_error_handler(actual_error));
|
||||
fmt::detail::parse_format_string<true>(s, checker);
|
||||
return equal(actual_error, expected_error);
|
||||
}
|
||||
|
||||
# define EXPECT_ERROR_NOARGS(fmt, error) \
|
||||
static_assert(test_error(fmt, error), "")
|
||||
# define EXPECT_ERROR(fmt, error, ...) \
|
||||
static_assert(test_error<__VA_ARGS__>(fmt, error), "")
|
||||
|
||||
TEST(format_test, format_string_errors) {
|
||||
EXPECT_ERROR_NOARGS("foo", nullptr);
|
||||
EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string");
|
||||
EXPECT_ERROR("{0:s", "unknown format specifier", date);
|
||||
# if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1916
|
||||
// This causes an detail compiler error in MSVC2017.
|
||||
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
|
||||
EXPECT_ERROR("{:10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR("{:.10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR_NOARGS("{:x}", "argument not found");
|
||||
EXPECT_ERROR("{:+}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{:-}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{:#}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{: }", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{:0}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{:{}}", "argument not found", int);
|
||||
EXPECT_ERROR("{:.{}}", "argument not found", double);
|
||||
EXPECT_ERROR("{:{}}", "width/precision is not integer", int, double);
|
||||
EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", int);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", char);
|
||||
EXPECT_ERROR("{:+}", "invalid format specifier for char", char);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", double);
|
||||
EXPECT_ERROR("{:d}", "invalid type specifier", const char*);
|
||||
EXPECT_ERROR("{:d}", "invalid type specifier", std::string);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", void*);
|
||||
# else
|
||||
fmt::print("warning: constexpr is broken in this version of MSVC\n");
|
||||
# endif
|
||||
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
using namespace fmt::literals;
|
||||
EXPECT_ERROR("{foo}", "named argument is not found", decltype("bar"_a = 42));
|
||||
EXPECT_ERROR("{foo}", "named argument is not found",
|
||||
decltype(fmt::arg("foo", 42)));
|
||||
# else
|
||||
EXPECT_ERROR("{foo}",
|
||||
"compile-time checks for named arguments require C++20 support",
|
||||
int);
|
||||
# endif
|
||||
EXPECT_ERROR_NOARGS("{10000000000}", "argument not found");
|
||||
EXPECT_ERROR_NOARGS("{0x}", "invalid format string");
|
||||
EXPECT_ERROR_NOARGS("{-}", "invalid format string");
|
||||
EXPECT_ERROR("{:{0x}}", "invalid format string", int);
|
||||
EXPECT_ERROR("{:{-}}", "invalid format string", int);
|
||||
EXPECT_ERROR("{:.{0x}}", "invalid format string", int);
|
||||
EXPECT_ERROR("{:.{-}}", "invalid format string", int);
|
||||
EXPECT_ERROR("{:.x}", "missing precision specifier", int);
|
||||
EXPECT_ERROR_NOARGS("{}", "argument not found");
|
||||
EXPECT_ERROR("{1}", "argument not found", int);
|
||||
EXPECT_ERROR("{1}{}",
|
||||
"cannot switch from manual to automatic argument indexing", int,
|
||||
int);
|
||||
EXPECT_ERROR("{}{1}",
|
||||
"cannot switch from automatic to manual argument indexing", int,
|
||||
int);
|
||||
}
|
||||
|
||||
TEST(format_test, vformat_to) {
|
||||
using context = fmt::format_context;
|
||||
fmt::basic_format_arg<context> arg = fmt::detail::make_arg<context>(42);
|
||||
auto args = fmt::basic_format_args<context>(&arg, 1);
|
||||
int n = 42;
|
||||
auto args = fmt::make_format_args<context>(n);
|
||||
auto s = std::string();
|
||||
fmt::vformat_to(std::back_inserter(s), "{}", args);
|
||||
EXPECT_EQ("42", s);
|
||||
@ -2230,8 +2085,6 @@ TEST(format_test, vformat_to) {
|
||||
EXPECT_EQ("42", s);
|
||||
}
|
||||
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
TEST(format_test, char_traits_is_not_ambiguous) {
|
||||
// Test that we don't inject detail names into the std namespace.
|
||||
using namespace std;
|
||||
@ -2248,7 +2101,7 @@ struct check_back_appender {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<check_back_appender> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
@ -2267,6 +2120,34 @@ TEST(format_test, back_insert_slicing) {
|
||||
EXPECT_EQ(fmt::format("{}", check_back_appender{}), "y");
|
||||
}
|
||||
|
||||
namespace test {
|
||||
enum class scoped_enum_as_int {};
|
||||
auto format_as(scoped_enum_as_int) -> int { return 42; }
|
||||
|
||||
enum class scoped_enum_as_string_view {};
|
||||
auto format_as(scoped_enum_as_string_view) -> fmt::string_view { return "foo"; }
|
||||
|
||||
enum class scoped_enum_as_string {};
|
||||
auto format_as(scoped_enum_as_string) -> std::string { return "foo"; }
|
||||
|
||||
struct struct_as_int {};
|
||||
auto format_as(struct_as_int) -> int { return 42; }
|
||||
} // namespace test
|
||||
|
||||
TEST(format_test, format_as) {
|
||||
EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_int()), "42");
|
||||
EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string_view()), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string()), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", test::struct_as_int()), "42");
|
||||
}
|
||||
|
||||
TEST(format_test, format_as_to_string) {
|
||||
EXPECT_EQ(fmt::to_string(test::scoped_enum_as_int()), "42");
|
||||
EXPECT_EQ(fmt::to_string(test::scoped_enum_as_string_view()), "foo");
|
||||
EXPECT_EQ(fmt::to_string(test::scoped_enum_as_string()), "foo");
|
||||
EXPECT_EQ(fmt::to_string(test::struct_as_int()), "42");
|
||||
}
|
||||
|
||||
template <typename Char, typename T> bool check_enabled_formatter() {
|
||||
static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value,
|
||||
"");
|
||||
@ -2309,3 +2190,78 @@ TEST(format_int_test, format_int) {
|
||||
os << max_value<int64_t>();
|
||||
EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
|
||||
}
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
# include <locale>
|
||||
|
||||
class format_facet : public fmt::format_facet<std::locale> {
|
||||
protected:
|
||||
struct int_formatter {
|
||||
fmt::appender out;
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integer<T>::value)>
|
||||
auto operator()(T value) -> bool {
|
||||
fmt::format_to(out, "[{}]", value);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!fmt::detail::is_integer<T>::value)>
|
||||
auto operator()(T) -> bool {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
auto do_put(fmt::appender out, fmt::loc_value val,
|
||||
const fmt::format_specs<>&) const -> bool override;
|
||||
};
|
||||
|
||||
auto format_facet::do_put(fmt::appender out, fmt::loc_value val,
|
||||
const fmt::format_specs<>&) const -> bool {
|
||||
return val.visit(int_formatter{out});
|
||||
}
|
||||
|
||||
TEST(format_test, format_facet) {
|
||||
auto loc = std::locale(std::locale(), new format_facet());
|
||||
EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]");
|
||||
EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]");
|
||||
}
|
||||
|
||||
TEST(format_test, format_facet_separator) {
|
||||
// U+2019 RIGHT SINGLE QUOTATION MARK is a digit separator in the de_CH
|
||||
// locale.
|
||||
auto loc =
|
||||
std::locale({}, new fmt::format_facet<std::locale>("\xe2\x80\x99"));
|
||||
EXPECT_EQ(fmt::format(loc, "{:L}", 1000),
|
||||
"1\xe2\x80\x99"
|
||||
"000");
|
||||
}
|
||||
|
||||
TEST(format_test, format_facet_grouping) {
|
||||
auto loc =
|
||||
std::locale({}, new fmt::format_facet<std::locale>(",", {1, 2, 3}));
|
||||
EXPECT_EQ(fmt::format(loc, "{:L}", 1234567890), "1,234,567,89,0");
|
||||
}
|
||||
|
||||
TEST(format_test, format_named_arg_with_locale) {
|
||||
EXPECT_EQ(fmt::format(std::locale(), "{answer}", fmt::arg("answer", 42)),
|
||||
"42");
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
struct convertible_to_nonconst_cstring {
|
||||
operator char*() const {
|
||||
static char c[] = "bar";
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <>
|
||||
struct formatter<convertible_to_nonconst_cstring> : formatter<char*> {};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(format_test, formatter_nonconst_char) {
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_nonconst_cstring()), "bar");
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ function(add_fuzzer source)
|
||||
if (FMT_FUZZ_LDFLAGS)
|
||||
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
|
||||
endif ()
|
||||
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
|
||||
target_compile_features(${name} PRIVATE cxx_std_14)
|
||||
endfunction()
|
||||
|
||||
foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)
|
||||
|
@ -201,6 +201,7 @@ TEST(gtest_extra_test, expect_write_streaming) {
|
||||
// EXPECT_THROW_MSG macro.
|
||||
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
||||
int n = 0;
|
||||
(void)n;
|
||||
using std::runtime_error;
|
||||
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
|
||||
@ -213,6 +214,7 @@ TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
||||
// EXPECT_SYSTEM_ERROR macro.
|
||||
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
|
||||
int n = 0;
|
||||
(void)n;
|
||||
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), "");
|
||||
|
@ -12,12 +12,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef FMT_MODULE_TEST
|
||||
import fmt;
|
||||
#else
|
||||
# include "fmt/os.h"
|
||||
#endif // FMG_MODULE_TEST
|
||||
|
||||
#include "fmt/os.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
||||
|
@ -9,6 +9,7 @@ add_library(gtest STATIC
|
||||
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
|
||||
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
|
||||
target_include_directories(gtest SYSTEM PUBLIC .)
|
||||
target_compile_features(gtest PUBLIC cxx_std_11)
|
||||
|
||||
find_package(Threads)
|
||||
if (Threads_FOUND)
|
||||
@ -17,13 +18,6 @@ else ()
|
||||
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
|
||||
endif ()
|
||||
|
||||
# Workaround GTest bug https://github.com/google/googletest/issues/705.
|
||||
fmt_check_cxx_compiler_flag(
|
||||
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
||||
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
||||
target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
||||
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
|
@ -13778,7 +13778,7 @@ UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith(
|
||||
|
||||
UntypedActionResultHolderBase* result = nullptr;
|
||||
|
||||
auto perform_action = [&] {
|
||||
auto perform_action = [&, this] {
|
||||
return untyped_action == nullptr
|
||||
? this->UntypedPerformDefaultAction(untyped_args, ss.str())
|
||||
: this->UntypedPerformAction(untyped_action, untyped_args);
|
||||
|
@ -20,8 +20,8 @@ template <typename T> class mock_allocator {
|
||||
mock_allocator() {}
|
||||
mock_allocator(const mock_allocator&) {}
|
||||
using value_type = T;
|
||||
MOCK_METHOD1_T(allocate, T*(size_t n));
|
||||
MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
|
||||
MOCK_METHOD(T*, allocate, (size_t));
|
||||
MOCK_METHOD(void, deallocate, (T*, size_t));
|
||||
};
|
||||
|
||||
template <typename Allocator> class allocator_ref {
|
||||
|
@ -15,8 +15,6 @@
|
||||
# define FMT_HIDE_MODULE_BUGS
|
||||
#endif
|
||||
|
||||
#define FMT_MODULE_TEST
|
||||
|
||||
#include <bit>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
@ -41,7 +39,6 @@
|
||||
#else
|
||||
# define FMT_POSIX(call) call
|
||||
#endif
|
||||
#define FMT_OS_H_ // don't pull in os.h directly or indirectly
|
||||
|
||||
import fmt;
|
||||
|
||||
@ -53,13 +50,8 @@ static bool macro_leaked =
|
||||
false;
|
||||
#endif
|
||||
|
||||
// Include sources to pick up functions and classes from the module rather than
|
||||
// from the non-modular library which is baked into the 'test-main' library.
|
||||
// This averts linker problems:
|
||||
// - strong ownership model: missing linker symbols
|
||||
// - weak ownership model: duplicate linker symbols
|
||||
#include "gtest-extra.cc"
|
||||
#include "util.cc"
|
||||
#define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly
|
||||
#include "gtest-extra.h"
|
||||
|
||||
// an implicitly exported namespace must be visible [module.interface]/2.2
|
||||
TEST(module_test, namespace) {
|
||||
@ -75,9 +67,8 @@ bool oops_detail_namespace_is_visible;
|
||||
namespace fmt {
|
||||
bool namespace_detail_invisible() {
|
||||
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
|
||||
((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \
|
||||
(_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704))
|
||||
// bug in msvc up to 16.11.5 / 17.0-pre5:
|
||||
_MSC_FULL_VER <= 193700000
|
||||
// bug in msvc up to at least 17.7:
|
||||
|
||||
// the namespace is visible even when it is neither
|
||||
// implicitly nor explicitly exported
|
||||
@ -111,7 +102,7 @@ TEST(module_test, macros) {
|
||||
// but rather visibility of all client-facing overloads, reachability of
|
||||
// non-exported entities, name lookup and overload resolution within
|
||||
// template instantitions.
|
||||
// Excercise all exported entities of the API at least once.
|
||||
// Exercise all exported entities of the API at least once.
|
||||
// Instantiate as many code paths as possible.
|
||||
|
||||
TEST(module_test, to_string) {
|
||||
@ -140,7 +131,7 @@ TEST(module_test, format_to) {
|
||||
EXPECT_EQ("42", std::string_view(buffer));
|
||||
|
||||
fmt::memory_buffer mb;
|
||||
fmt::format_to(mb, "{}", 42);
|
||||
fmt::format_to(std::back_inserter(mb), "{}", 42);
|
||||
EXPECT_EQ("42", std::string_view(buffer));
|
||||
|
||||
std::wstring w;
|
||||
@ -152,7 +143,7 @@ TEST(module_test, format_to) {
|
||||
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||
|
||||
fmt::wmemory_buffer wb;
|
||||
fmt::format_to(wb, L"{}", 42);
|
||||
fmt::format_to(std::back_inserter(wb), L"{}", 42);
|
||||
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||
}
|
||||
|
||||
@ -211,8 +202,8 @@ TEST(module_test, dynamic_format_args) {
|
||||
|
||||
TEST(module_test, vformat) {
|
||||
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
|
||||
EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"),
|
||||
fmt::make_wformat_args(42)));
|
||||
EXPECT_EQ(L"42",
|
||||
fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42)));
|
||||
}
|
||||
|
||||
TEST(module_test, vformat_to) {
|
||||
@ -245,9 +236,9 @@ TEST(module_test, vformat_to_n) {
|
||||
auto wstore = fmt::make_wformat_args(12345);
|
||||
std::wstring w;
|
||||
auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1,
|
||||
fmt::to_string_view(L"{}"), wstore);
|
||||
fmt::wstring_view(L"{}"), wstore);
|
||||
wchar_t wbuffer[4] = {0};
|
||||
fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore);
|
||||
fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore);
|
||||
}
|
||||
|
||||
std::string as_string(std::wstring_view text) {
|
||||
@ -258,22 +249,18 @@ std::string as_string(std::wstring_view text) {
|
||||
TEST(module_test, print) {
|
||||
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ");
|
||||
if (false) {
|
||||
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
|
||||
}
|
||||
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
|
||||
}
|
||||
|
||||
TEST(module_test, vprint) {
|
||||
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
|
||||
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
|
||||
"4.2");
|
||||
if (false) {
|
||||
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
|
||||
as_string(L"42µ"));
|
||||
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
|
||||
as_string(L"42"));
|
||||
}
|
||||
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
|
||||
as_string(L"42µ"));
|
||||
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
|
||||
as_string(L"42"));
|
||||
}
|
||||
|
||||
TEST(module_test, named_args) {
|
||||
@ -284,9 +271,7 @@ TEST(module_test, named_args) {
|
||||
TEST(module_test, literals) {
|
||||
using namespace fmt::literals;
|
||||
EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42));
|
||||
EXPECT_EQ("42", "{}"_format(42));
|
||||
EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42));
|
||||
EXPECT_EQ(L"42", L"{}"_format(42));
|
||||
}
|
||||
|
||||
TEST(module_test, locale) {
|
||||
@ -320,7 +305,7 @@ TEST(module_test, string_view) {
|
||||
|
||||
TEST(module_test, memory_buffer) {
|
||||
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
|
||||
fmt::format_to(buffer, "{}", "42");
|
||||
fmt::format_to(std::back_inserter(buffer), "{}", "42");
|
||||
EXPECT_EQ("42", to_string(buffer));
|
||||
fmt::memory_buffer nbuffer(std::move(buffer));
|
||||
EXPECT_EQ("42", to_string(nbuffer));
|
||||
@ -395,27 +380,20 @@ struct test_formatter : fmt::formatter<char> {
|
||||
bool check() { return true; }
|
||||
};
|
||||
|
||||
struct test_dynamic_formatter : fmt::dynamic_formatter<> {
|
||||
bool check() { return true; }
|
||||
};
|
||||
|
||||
TEST(module_test, formatter) {
|
||||
EXPECT_TRUE(test_formatter{}.check());
|
||||
EXPECT_TRUE(test_dynamic_formatter{}.check());
|
||||
}
|
||||
TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); }
|
||||
|
||||
TEST(module_test, join) {
|
||||
int arr[3] = {1, 2, 3};
|
||||
std::vector<double> vec{1.0, 2.0, 3.0};
|
||||
std::initializer_list<int> il{1, 2, 3};
|
||||
auto sep = fmt::to_string_view(", ");
|
||||
auto sep = fmt::string_view(", ");
|
||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep)));
|
||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep)));
|
||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep)));
|
||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep)));
|
||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep)));
|
||||
|
||||
auto wsep = fmt::to_string_view(L", ");
|
||||
auto wsep = fmt::wstring_view(L", ");
|
||||
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep)));
|
||||
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep)));
|
||||
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep)));
|
||||
@ -426,7 +404,6 @@ TEST(module_test, time) {
|
||||
EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120);
|
||||
EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120);
|
||||
auto chrono_now = std::chrono::system_clock::now();
|
||||
EXPECT_TRUE(fmt::localtime(chrono_now).tm_year > 120);
|
||||
EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120);
|
||||
}
|
||||
|
||||
@ -453,34 +430,16 @@ TEST(module_test, weekday) {
|
||||
EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
|
||||
}
|
||||
|
||||
TEST(module_test, to_string_view) {
|
||||
using fmt::to_string_view;
|
||||
fmt::string_view nsv{to_string_view("42")};
|
||||
EXPECT_EQ("42", nsv);
|
||||
fmt::wstring_view wsv{to_string_view(L"42")};
|
||||
EXPECT_EQ(L"42", wsv);
|
||||
}
|
||||
|
||||
TEST(module_test, printf) {
|
||||
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
|
||||
EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42");
|
||||
if (false) {
|
||||
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456),
|
||||
as_string(L"42.123456"));
|
||||
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
|
||||
}
|
||||
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), as_string(L"42.123456"));
|
||||
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
|
||||
}
|
||||
|
||||
TEST(module_test, fprintf) {
|
||||
EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42");
|
||||
std::ostringstream os;
|
||||
fmt::fprintf(os, "%s", "bla");
|
||||
EXPECT_EQ("bla", os.str());
|
||||
|
||||
EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42"));
|
||||
std::wostringstream ws;
|
||||
fmt::fprintf(ws, L"%s", L"bla");
|
||||
EXPECT_EQ(L"bla", ws.str());
|
||||
}
|
||||
|
||||
TEST(module_test, sprintf) {
|
||||
@ -490,25 +449,15 @@ TEST(module_test, sprintf) {
|
||||
|
||||
TEST(module_test, vprintf) {
|
||||
EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42");
|
||||
if (false) {
|
||||
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
|
||||
as_string(L"42"));
|
||||
}
|
||||
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
|
||||
as_string(L"42"));
|
||||
}
|
||||
|
||||
TEST(module_test, vfprintf) {
|
||||
auto args = fmt::make_printf_args(42);
|
||||
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42");
|
||||
std::ostringstream os;
|
||||
fmt::vfprintf(os, "%d", args);
|
||||
EXPECT_EQ("42", os.str());
|
||||
auto wargs = fmt::make_wprintf_args(42);
|
||||
if (false) {
|
||||
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
|
||||
}
|
||||
std::wostringstream ws;
|
||||
fmt::vfprintf(ws, L"%d", wargs);
|
||||
EXPECT_EQ(L"42", ws.str());
|
||||
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
|
||||
}
|
||||
|
||||
TEST(module_test, vsprintf) {
|
||||
@ -538,8 +487,13 @@ TEST(module_test, buffered_file) {
|
||||
}
|
||||
|
||||
TEST(module_test, output_file) {
|
||||
#ifdef __clang__
|
||||
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
|
||||
"Clang 16.0 emits multiple copies of vtables");
|
||||
#else
|
||||
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
|
||||
out.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
struct custom_context {
|
||||
@ -552,21 +506,15 @@ TEST(module_test, custom_context) {
|
||||
EXPECT_TRUE(!custom_arg);
|
||||
}
|
||||
|
||||
struct disabled_formatter {};
|
||||
|
||||
TEST(module_test, has_formatter) {
|
||||
EXPECT_FALSE(
|
||||
(fmt::has_formatter<disabled_formatter, fmt::format_context>::value));
|
||||
}
|
||||
|
||||
TEST(module_test, is_formattable) {
|
||||
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
|
||||
}
|
||||
|
||||
TEST(module_test, compile_format_string) {
|
||||
using namespace fmt::literals;
|
||||
#ifdef __clang__
|
||||
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
|
||||
"Clang 16.0 fails to import user-defined literals");
|
||||
#else
|
||||
EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42));
|
||||
EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42));
|
||||
EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2));
|
||||
EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42"));
|
||||
#endif
|
||||
}
|
||||
|
@ -22,60 +22,6 @@ using wstring_view = fmt::basic_string_view<wchar_t>;
|
||||
|
||||
# include <windows.h>
|
||||
|
||||
TEST(util_test, utf16_to_utf8) {
|
||||
auto s = std::string("ёжик");
|
||||
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
TEST(util_test, utf16_to_utf8_empty_string) {
|
||||
std::string s = "";
|
||||
fmt::detail::utf16_to_utf8 u(L"");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
template <typename Converter, typename Char>
|
||||
void check_utf_conversion_error(const char* message,
|
||||
fmt::basic_string_view<Char> str =
|
||||
fmt::basic_string_view<Char>(nullptr, 1)) {
|
||||
fmt::memory_buffer out;
|
||||
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
|
||||
auto error = std::system_error(std::error_code());
|
||||
try {
|
||||
(Converter)(str);
|
||||
} catch (const std::system_error& e) {
|
||||
error = e;
|
||||
}
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value());
|
||||
EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out)));
|
||||
}
|
||||
|
||||
TEST(util_test, utf16_to_utf8_error) {
|
||||
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
|
||||
"cannot convert string from UTF-16 to UTF-8");
|
||||
}
|
||||
|
||||
TEST(util_test, utf16_to_utf8_convert) {
|
||||
fmt::detail::utf16_to_utf8 u;
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1)));
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER,
|
||||
u.convert(wstring_view(L"foo", INT_MAX + 1u)));
|
||||
}
|
||||
|
||||
TEST(os_test, format_std_error_code) {
|
||||
EXPECT_EQ("generic:42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(42, std::generic_category())));
|
||||
EXPECT_EQ("system:42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(42, fmt::system_category())));
|
||||
EXPECT_EQ("system:-42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(-42, fmt::system_category())));
|
||||
}
|
||||
|
||||
TEST(os_test, format_windows_error) {
|
||||
LPWSTR message = nullptr;
|
||||
auto result = FormatMessageW(
|
||||
@ -83,7 +29,8 @@ TEST(os_test, format_windows_error) {
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPWSTR>(&message), 0, nullptr);
|
||||
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
|
||||
auto utf8_message =
|
||||
fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2));
|
||||
LocalFree(message);
|
||||
fmt::memory_buffer actual_message;
|
||||
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
|
||||
@ -108,7 +55,8 @@ TEST(os_test, format_long_windows_error) {
|
||||
LocalFree(message);
|
||||
return;
|
||||
}
|
||||
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
|
||||
auto utf8_message =
|
||||
fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2));
|
||||
LocalFree(message);
|
||||
fmt::memory_buffer actual_message;
|
||||
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
|
||||
@ -139,6 +87,17 @@ TEST(os_test, report_windows_error) {
|
||||
fmt::to_string(out));
|
||||
}
|
||||
|
||||
# if FMT_USE_FCNTL && !defined(__MINGW32__)
|
||||
TEST(file_test, open_windows_file) {
|
||||
using fmt::file;
|
||||
file out = file::open_windows_file(L"test-file",
|
||||
file::WRONLY | file::CREATE | file::TRUNC);
|
||||
out.write("x", 1);
|
||||
file in = file::open_windows_file(L"test-file", file::RDONLY);
|
||||
EXPECT_READ(in, "x");
|
||||
}
|
||||
# endif // FMT_USE_FCNTL && !defined(__MINGW32__)
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
|
@ -86,13 +86,13 @@ TEST(ostream_test, format_specs) {
|
||||
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
|
||||
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
|
||||
format_error, "format specifier requires numeric argument");
|
||||
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
|
||||
@ -106,9 +106,17 @@ TEST(ostream_test, empty_custom_output) {
|
||||
}
|
||||
|
||||
TEST(ostream_test, print) {
|
||||
std::ostringstream os;
|
||||
fmt::print(os, "Don't {}!", "panic");
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
{
|
||||
std::ostringstream os;
|
||||
fmt::print(os, "Don't {}!", "panic");
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream os;
|
||||
fmt::println(os, "Don't {}!", "panic");
|
||||
EXPECT_EQ("Don't panic!\n", os.str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ostream_test, write_to_ostream) {
|
||||
@ -132,7 +140,7 @@ TEST(ostream_test, write_to_ostream_max_size) {
|
||||
} buffer(max_size);
|
||||
|
||||
struct mock_streambuf : std::streambuf {
|
||||
MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n));
|
||||
MOCK_METHOD(std::streamsize, xsputn, (const void*, std::streamsize));
|
||||
auto xsputn(const char* s, std::streamsize n) -> std::streamsize override {
|
||||
const void* v = s;
|
||||
return xsputn(v, n);
|
||||
@ -218,45 +226,6 @@ TEST(ostream_test, format_to_n) {
|
||||
EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
|
||||
}
|
||||
|
||||
template <typename T> struct convertible {
|
||||
T value;
|
||||
explicit convertible(const T& val) : value(val) {}
|
||||
operator T() const { return value; }
|
||||
};
|
||||
|
||||
TEST(ostream_test, disable_builtin_ostream_operators) {
|
||||
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
|
||||
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
|
||||
}
|
||||
|
||||
struct streamable_and_convertible_to_bool {
|
||||
operator bool() const { return true; }
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
|
||||
return os << "foo";
|
||||
}
|
||||
|
||||
TEST(ostream_test, format_convertible_to_bool) {
|
||||
// operator<< is intentionally not used because of potential ODR violations.
|
||||
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true");
|
||||
}
|
||||
|
||||
struct streamable_and_convertible_to_string_view {
|
||||
operator fmt::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
streamable_and_convertible_to_string_view) {
|
||||
return os << "bar";
|
||||
}
|
||||
|
||||
TEST(ostream_test, format_convertible_to_string_vew) {
|
||||
// operator<< is intentionally not used because of potential ODR violations.
|
||||
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_string_view()),
|
||||
"foo");
|
||||
}
|
||||
|
||||
struct copyfmt_test {};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, copyfmt_test) {
|
||||
|
@ -72,12 +72,6 @@ int test::open(const char* path, int oflag, int mode) {
|
||||
EMULATE_EINTR(open, -1);
|
||||
return ::open(path, oflag, mode);
|
||||
}
|
||||
#else
|
||||
errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag,
|
||||
int pmode) {
|
||||
EMULATE_EINTR(open, EINTR);
|
||||
return _sopen_s(pfh, filename, oflag, shflag, pmode);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
@ -220,11 +214,11 @@ TEST(os_test, getpagesize) {
|
||||
}
|
||||
|
||||
TEST(file_test, open_retry) {
|
||||
# ifndef _WIN32
|
||||
write_file("temp", "there must be something here");
|
||||
std::unique_ptr<file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
|
||||
"cannot open file temp");
|
||||
# ifndef _WIN32
|
||||
char c = 0;
|
||||
f->read(&c, 1);
|
||||
# endif
|
||||
|
@ -37,8 +37,6 @@ int fstat(int fd, struct stat* buf);
|
||||
#else
|
||||
typedef unsigned size_t;
|
||||
typedef int ssize_t;
|
||||
errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag,
|
||||
int pmode);
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
|
@ -225,10 +225,8 @@ TEST(printf_test, hash_flag) {
|
||||
EXPECT_PRINTF("-42.0000", "%#g", -42.0);
|
||||
EXPECT_PRINTF("-42.0000", "%#G", -42.0);
|
||||
|
||||
safe_sprintf(buffer, "%#a", 16.0);
|
||||
EXPECT_PRINTF(buffer, "%#a", 16.0);
|
||||
safe_sprintf(buffer, "%#A", 16.0);
|
||||
EXPECT_PRINTF(buffer, "%#A", 16.0);
|
||||
EXPECT_PRINTF("0x1.p+4", "%#a", 16.0);
|
||||
EXPECT_PRINTF("0X1.P+4", "%#A", 16.0);
|
||||
|
||||
// '#' flag is ignored for non-numeric types.
|
||||
EXPECT_PRINTF("x", "%#c", 'x');
|
||||
@ -239,7 +237,7 @@ TEST(printf_test, width) {
|
||||
|
||||
// Width cannot be specified twice.
|
||||
EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
|
||||
"invalid type specifier");
|
||||
"invalid format specifier");
|
||||
|
||||
EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
|
||||
"number is too big");
|
||||
@ -407,10 +405,7 @@ TEST(printf_test, length) {
|
||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
||||
}
|
||||
|
||||
TEST(printf_test, bool) {
|
||||
EXPECT_PRINTF("1", "%d", true);
|
||||
EXPECT_PRINTF("true", "%s", true);
|
||||
}
|
||||
TEST(printf_test, bool) { EXPECT_PRINTF("1", "%d", true); }
|
||||
|
||||
TEST(printf_test, int) {
|
||||
EXPECT_PRINTF("-42", "%d", -42);
|
||||
@ -534,11 +529,12 @@ TEST(printf_test, wide_string) {
|
||||
}
|
||||
|
||||
TEST(printf_test, vprintf) {
|
||||
fmt::format_arg_store<fmt::printf_context, int> as{42};
|
||||
fmt::basic_format_args<fmt::printf_context> args(as);
|
||||
EXPECT_EQ(fmt::vsprintf("%d", args), "42");
|
||||
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
|
||||
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
|
||||
int n = 42;
|
||||
auto store = fmt::format_arg_store<fmt::printf_context, int>(n);
|
||||
auto args = fmt::basic_format_args<fmt::printf_context>(store);
|
||||
EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42");
|
||||
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args),
|
||||
"42");
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@ -554,31 +550,11 @@ TEST(printf_test, fixed_large_exponent) {
|
||||
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
|
||||
}
|
||||
|
||||
TEST(printf_test, vsprintf_make_args_example) {
|
||||
fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
|
||||
"something"};
|
||||
fmt::basic_format_args<fmt::printf_context> args(as);
|
||||
EXPECT_EQ("[42] something happened", fmt::vsprintf("[%d] %s happened", args));
|
||||
auto as2 = fmt::make_printf_args(42, "something");
|
||||
fmt::basic_format_args<fmt::printf_context> args2(as2);
|
||||
TEST(printf_test, make_printf_args) {
|
||||
EXPECT_EQ("[42] something happened",
|
||||
fmt::vsprintf("[%d] %s happened", args2));
|
||||
EXPECT_EQ("[42] something happened",
|
||||
fmt::vsprintf("[%d] %s happened",
|
||||
fmt::vsprintf(fmt::string_view("[%d] %s happened"),
|
||||
{fmt::make_printf_args(42, "something")}));
|
||||
}
|
||||
|
||||
TEST(printf_test, vsprintf_make_wargs_example) {
|
||||
fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
|
||||
42, L"something"};
|
||||
fmt::basic_format_args<fmt::wprintf_context> args(as);
|
||||
EXPECT_EQ(L"[42] something happened",
|
||||
fmt::vsprintf(L"[%d] %s happened", args));
|
||||
auto as2 = fmt::make_wprintf_args(42, L"something");
|
||||
fmt::basic_format_args<fmt::wprintf_context> args2(as2);
|
||||
EXPECT_EQ(L"[42] something happened",
|
||||
fmt::vsprintf(L"[%d] %s happened", args2));
|
||||
EXPECT_EQ(L"[42] something happened",
|
||||
fmt::vsprintf(L"[%d] %s happened",
|
||||
fmt::vsprintf(fmt::basic_string_view<wchar_t>(L"[%d] %s happened"),
|
||||
{fmt::make_wprintf_args(42, L"something")}));
|
||||
}
|
||||
|
@ -12,7 +12,10 @@
|
||||
#include "fmt/ranges.h"
|
||||
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
@ -50,6 +53,14 @@ TEST(ranges_test, format_vector) {
|
||||
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
||||
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
|
||||
EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
|
||||
|
||||
auto vc = std::vector<char>{'a', 'b', 'c'};
|
||||
auto vvc = std::vector<std::vector<char>>{vc, vc};
|
||||
EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']");
|
||||
EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]");
|
||||
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
|
||||
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
|
||||
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
|
||||
}
|
||||
|
||||
TEST(ranges_test, format_vector2) {
|
||||
@ -70,6 +81,35 @@ TEST(ranges_test, format_set) {
|
||||
"{\"one\", \"two\"}");
|
||||
}
|
||||
|
||||
// Models std::flat_set close enough to test if no ambiguous lookup of a
|
||||
// formatter happens due to the flat_set type matching is_set and
|
||||
// is_container_adaptor_like
|
||||
template <typename T> class flat_set {
|
||||
public:
|
||||
using key_type = T;
|
||||
using container_type = std::vector<T>;
|
||||
|
||||
using iterator = typename std::vector<T>::iterator;
|
||||
using const_iterator = typename std::vector<T>::const_iterator;
|
||||
|
||||
template <typename... Ts>
|
||||
explicit flat_set(Ts&&... args) : c{std::forward<Ts>(args)...} {}
|
||||
|
||||
iterator begin() { return c.begin(); }
|
||||
const_iterator begin() const { return c.begin(); }
|
||||
|
||||
iterator end() { return c.end(); }
|
||||
const_iterator end() const { return c.end(); }
|
||||
|
||||
private:
|
||||
std::vector<T> c;
|
||||
};
|
||||
|
||||
TEST(ranges_test, format_flat_set) {
|
||||
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
|
||||
"{\"one\", \"two\"}");
|
||||
}
|
||||
|
||||
namespace adl {
|
||||
struct box {
|
||||
int value;
|
||||
@ -96,6 +136,7 @@ TEST(ranges_test, format_tuple) {
|
||||
auto t =
|
||||
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
|
||||
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
|
||||
|
||||
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
|
||||
@ -108,6 +149,25 @@ TEST(ranges_test, format_tuple) {
|
||||
EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
|
||||
}
|
||||
|
||||
struct not_default_formattable {};
|
||||
struct bad_format {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<not_default_formattable> {
|
||||
auto parse(format_parse_context&) -> const char* { throw bad_format(); }
|
||||
auto format(not_default_formattable, format_context& ctx)
|
||||
-> format_context::iterator {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(ranges_test, tuple_parse_calls_element_parse) {
|
||||
auto f = fmt::formatter<std::tuple<not_default_formattable>>();
|
||||
auto ctx = fmt::format_parse_context("");
|
||||
EXPECT_THROW(f.parse(ctx), bad_format);
|
||||
}
|
||||
|
||||
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
||||
struct tuple_like {
|
||||
int i;
|
||||
@ -149,32 +209,24 @@ TEST(ranges_test, format_to) {
|
||||
EXPECT_STREQ(buf, "[1, 2, 3]");
|
||||
}
|
||||
|
||||
struct path_like {
|
||||
template <typename Char> struct path_like {
|
||||
const path_like* begin() const;
|
||||
const path_like* end() const;
|
||||
|
||||
operator std::string() const;
|
||||
operator std::basic_string<Char>() const;
|
||||
};
|
||||
|
||||
TEST(ranges_test, path_like) {
|
||||
EXPECT_FALSE((fmt::is_range<path_like, char>::value));
|
||||
TEST(ranges_test, disabled_range_formatting_of_path) {
|
||||
// Range formatting of path is disabled because of infinite recursion
|
||||
// (path element is itself a path).
|
||||
EXPECT_EQ((fmt::range_format_kind<path_like<char>, char>::value),
|
||||
fmt::range_format::disabled);
|
||||
EXPECT_EQ((fmt::range_format_kind<path_like<wchar_t>, char>::value),
|
||||
fmt::range_format::disabled);
|
||||
}
|
||||
|
||||
#ifdef FMT_USE_STRING_VIEW
|
||||
struct string_like {
|
||||
const char* begin();
|
||||
const char* end();
|
||||
operator fmt::string_view() const { return "foo"; }
|
||||
operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(ranges_test, format_string_like) {
|
||||
EXPECT_EQ(fmt::format("{}", string_like()), "foo");
|
||||
}
|
||||
#endif // FMT_USE_STRING_VIEW
|
||||
|
||||
// A range that provides non-const only begin()/end() to test fmt::join handles
|
||||
// that.
|
||||
// A range that provides non-const only begin()/end() to test fmt::join
|
||||
// handles that.
|
||||
//
|
||||
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
|
||||
// information during iteration so they only provide non-const begin()/end().
|
||||
@ -198,7 +250,7 @@ template <typename T> class noncopyable_range {
|
||||
std::vector<T> vec;
|
||||
|
||||
public:
|
||||
using const_iterator = typename ::std::vector<T>::const_iterator;
|
||||
using iterator = typename ::std::vector<T>::iterator;
|
||||
|
||||
template <typename... Args>
|
||||
explicit noncopyable_range(Args&&... args)
|
||||
@ -207,8 +259,8 @@ template <typename T> class noncopyable_range {
|
||||
noncopyable_range(noncopyable_range const&) = delete;
|
||||
noncopyable_range(noncopyable_range&) = delete;
|
||||
|
||||
const_iterator begin() const { return vec.begin(); }
|
||||
const_iterator end() const { return vec.end(); }
|
||||
iterator begin() { return vec.begin(); }
|
||||
iterator end() { return vec.end(); }
|
||||
};
|
||||
|
||||
TEST(ranges_test, range) {
|
||||
@ -237,7 +289,6 @@ TEST(ranges_test, enum_range) {
|
||||
}
|
||||
|
||||
#if !FMT_MSC_VERSION
|
||||
|
||||
TEST(ranges_test, unformattable_range) {
|
||||
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
|
||||
fmt::format_context>::value));
|
||||
@ -369,7 +420,7 @@ TEST(ranges_test, is_printable) {
|
||||
EXPECT_FALSE(is_printable(0x110000));
|
||||
}
|
||||
|
||||
TEST(ranges_test, escape_string) {
|
||||
TEST(ranges_test, escape) {
|
||||
using vec = std::vector<std::string>;
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
|
||||
@ -389,20 +440,13 @@ TEST(ranges_test, escape_string) {
|
||||
"[\"\\xf0(\\x00\\x00anything\"]");
|
||||
|
||||
// Correct utf-8.
|
||||
EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]");
|
||||
EXPECT_EQ(fmt::format("{}", vec{"🦄"}), "[\"🦄\"]");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FMT_USE_STRING_VIEW
|
||||
struct convertible_to_string_view {
|
||||
operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(ranges_test, escape_convertible_to_string_view) {
|
||||
EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)),
|
||||
"[\"foo\"]");
|
||||
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
|
||||
"[['x']]");
|
||||
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
|
||||
}
|
||||
#endif // FMT_USE_STRING_VIEW
|
||||
|
||||
template <typename R> struct fmt_ref_view {
|
||||
R* r;
|
||||
@ -422,3 +466,62 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
|
||||
TEST(ranges_test, vector_char) {
|
||||
EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
|
||||
}
|
||||
|
||||
TEST(ranges_test, container_adaptor) {
|
||||
{
|
||||
using fmt::detail::is_container_adaptor_like;
|
||||
using T = std::nullptr_t;
|
||||
static_assert(is_container_adaptor_like<std::stack<T>>::value, "");
|
||||
static_assert(is_container_adaptor_like<std::queue<T>>::value, "");
|
||||
static_assert(is_container_adaptor_like<std::priority_queue<T>>::value, "");
|
||||
static_assert(!is_container_adaptor_like<std::vector<T>>::value, "");
|
||||
}
|
||||
|
||||
{
|
||||
auto s = std::stack<int>();
|
||||
s.push(1);
|
||||
s.push(2);
|
||||
EXPECT_EQ(fmt::format("{}", s), "[1, 2]");
|
||||
EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]");
|
||||
}
|
||||
|
||||
{
|
||||
auto q = std::queue<int>();
|
||||
q.push(1);
|
||||
q.push(2);
|
||||
EXPECT_EQ(fmt::format("{}", q), "[1, 2]");
|
||||
}
|
||||
|
||||
{
|
||||
auto q = std::priority_queue<int>();
|
||||
q.push(3);
|
||||
q.push(1);
|
||||
q.push(2);
|
||||
q.push(4);
|
||||
EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]");
|
||||
}
|
||||
|
||||
{
|
||||
auto s = std::stack<char, std::string>();
|
||||
s.push('a');
|
||||
s.push('b');
|
||||
// See https://cplusplus.github.io/LWG/issue3881.
|
||||
EXPECT_EQ(fmt::format("{}", s), "['a', 'b']");
|
||||
}
|
||||
|
||||
{
|
||||
struct my_container_adaptor {
|
||||
using value_type = int;
|
||||
using container_type = std::vector<value_type>;
|
||||
void push(const value_type& v) { c.push_back(v); }
|
||||
|
||||
protected:
|
||||
container_type c;
|
||||
};
|
||||
|
||||
auto m = my_container_adaptor();
|
||||
m.push(1);
|
||||
m.push(2);
|
||||
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ TEST(scan_test, read_string_view) {
|
||||
EXPECT_EQ(s, "foo");
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef FMT_HAVE_STRPTIME
|
||||
namespace fmt {
|
||||
template <> struct scanner<tm> {
|
||||
std::string format;
|
||||
|
31
test/scan.h
31
test/scan.h
@ -42,7 +42,7 @@ struct scan_context {
|
||||
public:
|
||||
using iterator = const char*;
|
||||
|
||||
explicit scan_context(string_view input) : input_(input) {}
|
||||
explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {}
|
||||
|
||||
iterator begin() const { return input_.data(); }
|
||||
iterator end() const { return begin() + input_.size(); }
|
||||
@ -83,17 +83,21 @@ class scan_arg {
|
||||
// TODO: more types
|
||||
};
|
||||
|
||||
scan_arg() : type(scan_type::none_type) {}
|
||||
scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {}
|
||||
scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {}
|
||||
scan_arg(long long& value)
|
||||
FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {}
|
||||
FMT_CONSTEXPR scan_arg(int& value)
|
||||
: type(scan_type::int_type), int_value(&value) {}
|
||||
FMT_CONSTEXPR scan_arg(unsigned& value)
|
||||
: type(scan_type::uint_type), uint_value(&value) {}
|
||||
FMT_CONSTEXPR scan_arg(long long& value)
|
||||
: type(scan_type::long_long_type), long_long_value(&value) {}
|
||||
scan_arg(unsigned long long& value)
|
||||
FMT_CONSTEXPR scan_arg(unsigned long long& value)
|
||||
: type(scan_type::ulong_long_type), ulong_long_value(&value) {}
|
||||
scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {}
|
||||
scan_arg(fmt::string_view& value)
|
||||
FMT_CONSTEXPR scan_arg(std::string& value)
|
||||
: type(scan_type::string_type), string(&value) {}
|
||||
FMT_CONSTEXPR scan_arg(fmt::string_view& value)
|
||||
: type(scan_type::string_view_type), string_view(&value) {}
|
||||
template <typename T> scan_arg(T& value) : type(scan_type::custom_type) {
|
||||
template <typename T>
|
||||
FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) {
|
||||
custom.value = &value;
|
||||
custom.scan = scan_custom_arg<T>;
|
||||
}
|
||||
@ -114,7 +118,7 @@ struct scan_args {
|
||||
const detail::scan_arg* data;
|
||||
|
||||
template <size_t N>
|
||||
scan_args(const std::array<detail::scan_arg, N>& store)
|
||||
FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store)
|
||||
: size(N), data(store.data()) {
|
||||
static_assert(N < INT_MAX, "too many arguments");
|
||||
}
|
||||
@ -154,7 +158,8 @@ struct scan_handler : error_handler {
|
||||
}
|
||||
|
||||
public:
|
||||
scan_handler(string_view format, string_view input, scan_args args)
|
||||
FMT_CONSTEXPR scan_handler(string_view format, string_view input,
|
||||
scan_args args)
|
||||
: parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
|
||||
|
||||
const char* pos() const { return scan_ctx_.begin(); }
|
||||
@ -162,10 +167,8 @@ struct scan_handler : error_handler {
|
||||
void on_text(const char* begin, const char* end) {
|
||||
auto size = to_unsigned(end - begin);
|
||||
auto it = scan_ctx_.begin();
|
||||
if (it + size > scan_ctx_.end() ||
|
||||
!std::equal(begin, end, make_checked(it, size))) {
|
||||
if (it + size > scan_ctx_.end() || !std::equal(begin, end, it))
|
||||
on_error("invalid input");
|
||||
}
|
||||
scan_ctx_.advance_to(it + size);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
cmake_minimum_required(VERSION 3.8...3.25)
|
||||
|
||||
project(fmt-link CXX)
|
||||
|
||||
|
218
test/std-test.cc
218
test/std-test.cc
@ -6,47 +6,110 @@
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/std.h"
|
||||
#include "fmt/ranges.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "fmt/os.h" // fmt::system_category
|
||||
#include "fmt/ranges.h"
|
||||
#include "gtest-extra.h" // StartsWith
|
||||
|
||||
TEST(std_test, path) {
|
||||
#ifdef __cpp_lib_filesystem
|
||||
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
|
||||
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")),
|
||||
"\"foo\\\"bar.txt\"");
|
||||
TEST(std_test, path) {
|
||||
using std::filesystem::path;
|
||||
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
|
||||
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
|
||||
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar");
|
||||
EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\"");
|
||||
|
||||
# ifdef _WIN32
|
||||
// File.txt in Russian.
|
||||
const wchar_t unicode_path[] = {0x424, 0x430, 0x439, 0x43b, 0x2e,
|
||||
0x74, 0x78, 0x74, 0};
|
||||
const char unicode_u8path[] = {'"', char(0xd0), char(0xa4), char(0xd0),
|
||||
char(0xb0), char(0xd0), char(0xb9), char(0xd0),
|
||||
char(0xbb), '.', 't', 'x',
|
||||
't', '"', '\0'};
|
||||
EXPECT_EQ(fmt::format("{}", std::filesystem::path(unicode_path)),
|
||||
unicode_u8path);
|
||||
EXPECT_EQ(fmt::format("{}", path(
|
||||
L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
|
||||
L"\x0447\x044B\x043D\x0430")),
|
||||
"Шчучыншчына");
|
||||
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
|
||||
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
|
||||
# endif
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ranges_std_test, format_vector_path) {
|
||||
// Test ambiguity problem described in #2954.
|
||||
#ifdef __cpp_lib_filesystem
|
||||
TEST(ranges_std_test, format_vector_path) {
|
||||
auto p = std::filesystem::path("foo/bar.txt");
|
||||
auto c = std::vector<std::string>{"abc", "def"};
|
||||
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
|
||||
"path=\"foo/bar.txt\", range=[\"abc\", \"def\"]");
|
||||
#endif
|
||||
"path=foo/bar.txt, range=[\"abc\", \"def\"]");
|
||||
}
|
||||
|
||||
// Test that path is not escaped twice in the debug mode.
|
||||
TEST(ranges_std_test, format_quote_path) {
|
||||
auto vec =
|
||||
std::vector<std::filesystem::path>{"path1/file1.txt", "path2/file2.txt"};
|
||||
EXPECT_EQ(fmt::format("{}", vec),
|
||||
"[\"path1/file1.txt\", \"path2/file2.txt\"]");
|
||||
# ifdef __cpp_lib_optional
|
||||
auto o = std::optional<std::filesystem::path>("path/file.txt");
|
||||
EXPECT_EQ(fmt::format("{}", o), "optional(\"path/file.txt\")");
|
||||
EXPECT_EQ(fmt::format("{:?}", o), "optional(\"path/file.txt\")");
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(std_test, thread_id) {
|
||||
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
|
||||
}
|
||||
|
||||
TEST(std_test, optional) {
|
||||
#ifdef __cpp_lib_optional
|
||||
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
|
||||
EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")");
|
||||
EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2},
|
||||
std::optional{3}}),
|
||||
"[optional(1), optional(2), optional(3)]");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}),
|
||||
"optional(optional(\"nested\"))");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30),
|
||||
"optional(\"left aligned\" )");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}),
|
||||
"optional([104, 101, 108, 108, 111])");
|
||||
EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}),
|
||||
"optional(\"string\")");
|
||||
EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')");
|
||||
EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)");
|
||||
|
||||
struct unformattable {};
|
||||
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
||||
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
|
||||
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
|
||||
#endif
|
||||
}
|
||||
|
||||
struct throws_on_move {
|
||||
throws_on_move() = default;
|
||||
|
||||
[[noreturn]] throws_on_move(throws_on_move&&) {
|
||||
throw std::runtime_error("Thrown by throws_on_move");
|
||||
}
|
||||
|
||||
throws_on_move(const throws_on_move&) = default;
|
||||
};
|
||||
|
||||
namespace fmt {
|
||||
template <> struct formatter<throws_on_move> : formatter<string_view> {
|
||||
auto format(const throws_on_move&, format_context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
string_view str("<throws_on_move>");
|
||||
return formatter<string_view>::format(str, ctx);
|
||||
}
|
||||
};
|
||||
} // namespace fmt
|
||||
|
||||
TEST(std_test, variant) {
|
||||
#ifdef __cpp_lib_variant
|
||||
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
|
||||
@ -75,5 +138,120 @@ TEST(std_test, variant) {
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
|
||||
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
|
||||
|
||||
volatile int i = 42; // Test compile error before GCC 11 described in #3068.
|
||||
EXPECT_EQ(fmt::format("{}", i), "42");
|
||||
|
||||
std::variant<std::monostate, throws_on_move> v6;
|
||||
|
||||
try {
|
||||
throws_on_move thrower;
|
||||
v6.emplace<throws_on_move>(std::move(thrower));
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
// v6 is now valueless by exception
|
||||
|
||||
EXPECT_EQ(fmt::format("{}", v6), "variant(valueless by exception)");
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(std_test, error_code) {
|
||||
EXPECT_EQ("generic:42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(42, std::generic_category())));
|
||||
EXPECT_EQ("system:42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(42, fmt::system_category())));
|
||||
EXPECT_EQ("system:-42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(-42, fmt::system_category())));
|
||||
}
|
||||
|
||||
template <typename Catch> void exception_test() {
|
||||
try {
|
||||
throw std::runtime_error("Test Exception");
|
||||
} catch (const Catch& ex) {
|
||||
EXPECT_EQ("Test Exception", fmt::format("{}", ex));
|
||||
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
namespace my_ns1 {
|
||||
namespace my_ns2 {
|
||||
struct my_exception : public std::exception {
|
||||
private:
|
||||
std::string msg;
|
||||
|
||||
public:
|
||||
my_exception(const std::string& s) : msg(s) {}
|
||||
const char* what() const noexcept override;
|
||||
};
|
||||
const char* my_exception::what() const noexcept { return msg.c_str(); }
|
||||
} // namespace my_ns2
|
||||
} // namespace my_ns1
|
||||
|
||||
TEST(std_test, exception) {
|
||||
using testing::StartsWith;
|
||||
exception_test<std::exception>();
|
||||
exception_test<std::runtime_error>();
|
||||
|
||||
try {
|
||||
using namespace my_ns1::my_ns2;
|
||||
throw my_exception("My Exception");
|
||||
} catch (const std::exception& ex) {
|
||||
EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception",
|
||||
fmt::format("{:t}", ex));
|
||||
EXPECT_EQ("My Exception", fmt::format("{:}", ex));
|
||||
}
|
||||
|
||||
try {
|
||||
throw std::system_error(std::error_code(), "message");
|
||||
} catch (const std::system_error& ex) {
|
||||
EXPECT_THAT(fmt::format("{:t}", ex), StartsWith("std::system_error: "));
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_filesystem
|
||||
// Tests that the inline namespace is stripped out, e.g.
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*.
|
||||
try {
|
||||
throw std::filesystem::filesystem_error("message", std::error_code());
|
||||
} catch (const std::filesystem::filesystem_error& ex) {
|
||||
EXPECT_THAT(fmt::format("{:t}", ex),
|
||||
StartsWith("std::filesystem::filesystem_error: "));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(std_test, format_bit_reference) {
|
||||
std::bitset<2> bs(1);
|
||||
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
|
||||
std::vector<bool> v = {true, false};
|
||||
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
|
||||
}
|
||||
|
||||
TEST(std_test, format_const_bit_reference) {
|
||||
const std::bitset<2> bs(1);
|
||||
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
|
||||
const std::vector<bool> v = {true, false};
|
||||
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
|
||||
}
|
||||
|
||||
TEST(std_test, format_atomic) {
|
||||
std::atomic<bool> b(false);
|
||||
EXPECT_EQ(fmt::format("{}", b), "false");
|
||||
|
||||
const std::atomic<bool> cb(true);
|
||||
EXPECT_EQ(fmt::format("{}", cb), "true");
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
TEST(std_test, format_atomic_flag) {
|
||||
std::atomic_flag f = ATOMIC_FLAG_INIT;
|
||||
(void) f.test_and_set();
|
||||
EXPECT_EQ(fmt::format("{}", f), "true");
|
||||
|
||||
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
|
||||
EXPECT_EQ(fmt::format("{}", cf), "false");
|
||||
}
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
@ -10,11 +10,7 @@
|
||||
#include <locale>
|
||||
#include <string>
|
||||
|
||||
#ifdef FMT_MODULE_TEST
|
||||
import fmt;
|
||||
#else
|
||||
# include "fmt/os.h"
|
||||
#endif // FMT_MODULE_TEST
|
||||
#include "fmt/os.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define FMT_VSNPRINTF vsprintf_s
|
||||
@ -81,5 +77,5 @@ class date {
|
||||
int day() const { return day_; }
|
||||
};
|
||||
|
||||
// Returns a locale with the given name if available or classic locale othewise.
|
||||
// Returns a locale with the given name if available or classic locale otherwise.
|
||||
std::locale get_locale(const char* name, const char* alt_name = nullptr);
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
#include <cwchar>
|
||||
#include <vector>
|
||||
@ -15,31 +16,21 @@
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/ostream.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "fmt/std.h"
|
||||
#include "gtest-extra.h" // Contains
|
||||
#include "util.h" // get_locale
|
||||
|
||||
using fmt::detail::max_value;
|
||||
using testing::Contains;
|
||||
|
||||
namespace test_ns {
|
||||
template <typename Char> class test_string {
|
||||
private:
|
||||
std::basic_string<Char> s_;
|
||||
|
||||
public:
|
||||
test_string(const Char* s) : s_(s) {}
|
||||
const Char* data() const { return s_.data(); }
|
||||
size_t length() const { return s_.size(); }
|
||||
operator const Char*() const { return s_.c_str(); }
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
|
||||
return {s.data(), s.length()};
|
||||
}
|
||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
# define FMT_HAS_C99_STRFTIME 0
|
||||
#else
|
||||
# define FMT_HAS_C99_STRFTIME 1
|
||||
#endif
|
||||
|
||||
struct non_string {};
|
||||
} // namespace test_ns
|
||||
|
||||
template <typename T> class is_string_test : public testing::Test {};
|
||||
|
||||
@ -61,8 +52,7 @@ TYPED_TEST(is_string_test, is_string) {
|
||||
using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
|
||||
EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
|
||||
fmt::detail::is_string<fmt_string_view>::value);
|
||||
EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value);
|
||||
EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value);
|
||||
EXPECT_FALSE(fmt::detail::is_string<non_string>::value);
|
||||
}
|
||||
|
||||
// std::is_constructible is broken in MSVC until version 2015.
|
||||
@ -112,10 +102,12 @@ struct custom_char {
|
||||
template <typename T>
|
||||
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
|
||||
|
||||
operator int() const { return value; }
|
||||
operator char() const {
|
||||
return value <= 0xff ? static_cast<char>(value) : '\0';
|
||||
}
|
||||
};
|
||||
|
||||
int to_ascii(custom_char c) { return c; }
|
||||
auto to_ascii(custom_char c) -> char { return c; }
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct is_char<custom_char> : std::true_type {};
|
||||
@ -154,17 +146,21 @@ TEST(xchar_test, format_to) {
|
||||
}
|
||||
|
||||
TEST(xchar_test, vformat_to) {
|
||||
using wcontext = fmt::wformat_context;
|
||||
fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42);
|
||||
auto wargs = fmt::basic_format_args<wcontext>(&warg, 1);
|
||||
auto args = fmt::make_wformat_args(42);
|
||||
auto w = std::wstring();
|
||||
fmt::vformat_to(std::back_inserter(w), L"{}", wargs);
|
||||
EXPECT_EQ(L"42", w);
|
||||
w.clear();
|
||||
fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs);
|
||||
fmt::vformat_to(std::back_inserter(w), L"{}", args);
|
||||
EXPECT_EQ(L"42", w);
|
||||
}
|
||||
|
||||
namespace test {
|
||||
struct struct_as_wstring_view {};
|
||||
auto format_as(struct_as_wstring_view) -> fmt::wstring_view { return L"foo"; }
|
||||
} // namespace test
|
||||
|
||||
TEST(xchar_test, format_as) {
|
||||
EXPECT_EQ(fmt::format(L"{}", test::struct_as_wstring_view()), L"foo");
|
||||
}
|
||||
|
||||
TEST(format_test, wide_format_to_n) {
|
||||
wchar_t buffer[4];
|
||||
buffer[3] = L'x';
|
||||
@ -200,7 +196,10 @@ TEST(xchar_test, named_arg_udl) {
|
||||
|
||||
TEST(xchar_test, print) {
|
||||
// Check that the wide print overload compiles.
|
||||
if (fmt::detail::const_check(false)) fmt::print(L"test");
|
||||
if (fmt::detail::const_check(false)) {
|
||||
fmt::print(L"test");
|
||||
fmt::println(L"test");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(xchar_test, join) {
|
||||
@ -284,7 +283,8 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
|
||||
}
|
||||
|
||||
TEST(chrono_test_wchar, time_point) {
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now());
|
||||
|
||||
std::vector<std::wstring> spec_list = {
|
||||
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
|
||||
@ -292,19 +292,36 @@ TEST(chrono_test_wchar, time_point) {
|
||||
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
|
||||
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
|
||||
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
|
||||
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
|
||||
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows, because these formats is not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
|
||||
#elif defined(__MINGW32__) && !defined(_UCRT)
|
||||
#elif !FMT_HAS_C99_STRFTIME
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
|
||||
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
|
||||
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p", L"%Z"};
|
||||
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
|
||||
#endif
|
||||
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
auto sys_output = system_wcsftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
|
||||
// Timezone formatters tests makes sense for localtime.
|
||||
#if FMT_HAS_C99_STRFTIME
|
||||
spec_list = {L"%z", L"%Z"};
|
||||
#else
|
||||
spec_list = {L"%Z"};
|
||||
#endif
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::localtime(&t);
|
||||
@ -312,8 +329,38 @@ TEST(chrono_test_wchar, time_point) {
|
||||
auto sys_output = system_wcsftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
|
||||
if (spec == L"%z") {
|
||||
sys_output.insert(sys_output.end() - 2, 1, L':');
|
||||
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
|
||||
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
||||
// the timezone in std::tm (if it presents on platform).
|
||||
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
|
||||
}
|
||||
|
||||
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
|
||||
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
|
||||
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
|
||||
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,9 +371,17 @@ TEST(xchar_test, color) {
|
||||
|
||||
TEST(xchar_test, ostream) {
|
||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
|
||||
std::wostringstream wos;
|
||||
fmt::print(wos, L"Don't {}!", L"panic");
|
||||
EXPECT_EQ(wos.str(), L"Don't panic!");
|
||||
{
|
||||
std::wostringstream wos;
|
||||
fmt::print(wos, L"Don't {}!", L"panic");
|
||||
EXPECT_EQ(wos.str(), L"Don't panic!");
|
||||
}
|
||||
|
||||
{
|
||||
std::wostringstream wos;
|
||||
fmt::println(wos, L"Don't {}!", L"panic");
|
||||
EXPECT_EQ(wos.str(), L"Don't panic!\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -344,6 +399,7 @@ TEST(xchar_test, escape_string) {
|
||||
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
template <typename Char> struct numpunct : std::numpunct<Char> {
|
||||
protected:
|
||||
Char do_decimal_point() const override { return '?'; }
|
||||
@ -379,6 +435,9 @@ TEST(locale_test, localized_double) {
|
||||
EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
|
||||
EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
|
||||
EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230");
|
||||
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 0.1), " 0?100000");
|
||||
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1.0), " 1?000000");
|
||||
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1e3), " 1~000?000000");
|
||||
}
|
||||
|
||||
TEST(locale_test, format) {
|
||||
@ -387,8 +446,8 @@ TEST(locale_test, format) {
|
||||
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
|
||||
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
|
||||
EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
|
||||
fmt::format_arg_store<fmt::format_context, int> as{1234567};
|
||||
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
|
||||
auto n = 1234567;
|
||||
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::make_format_args(n)));
|
||||
auto s = std::string();
|
||||
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
|
||||
EXPECT_EQ("1~234~567", s);
|
||||
@ -421,10 +480,9 @@ TEST(locale_test, wformat) {
|
||||
auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
|
||||
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
|
||||
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
|
||||
using wcontext = fmt::buffer_context<wchar_t>;
|
||||
fmt::format_arg_store<wcontext, int> as{1234567};
|
||||
int n = 1234567;
|
||||
EXPECT_EQ(L"1~234~567",
|
||||
fmt::vformat(loc, L"{:L}", fmt::basic_format_args<wcontext>(as)));
|
||||
fmt::vformat(loc, L"{:L}", fmt::make_wformat_args(n)));
|
||||
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
|
||||
|
||||
auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>());
|
||||
@ -441,16 +499,16 @@ TEST(locale_test, wformat) {
|
||||
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
|
||||
}
|
||||
|
||||
TEST(locale_test, double_formatter) {
|
||||
TEST(locale_test, int_formatter) {
|
||||
auto loc = std::locale(std::locale(), new special_grouping<char>());
|
||||
auto f = fmt::formatter<int>();
|
||||
auto parse_ctx = fmt::format_parse_context("L");
|
||||
f.parse(parse_ctx);
|
||||
char buf[10] = {};
|
||||
fmt::basic_format_context<char*, char> format_ctx(
|
||||
buf, {}, fmt::detail::locale_ref(loc));
|
||||
*f.format(12345, format_ctx) = 0;
|
||||
EXPECT_STREQ("12,345", buf);
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::basic_format_context<fmt::appender, char> format_ctx(
|
||||
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
|
||||
f.format(12345, format_ctx);
|
||||
EXPECT_EQ(fmt::to_string(buf), "12,345");
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
@ -461,13 +519,10 @@ template <class charT> struct formatter<std::complex<double>, charT> {
|
||||
public:
|
||||
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
|
||||
basic_format_parse_context<charT>& ctx) {
|
||||
using handler_type =
|
||||
detail::dynamic_specs_handler<basic_format_parse_context<charT>>;
|
||||
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
|
||||
detail::type::string_type);
|
||||
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
|
||||
detail::parse_float_type_spec(specs_, ctx.error_handler());
|
||||
return it;
|
||||
auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
|
||||
detail::type::float_type);
|
||||
detail::parse_float_type_spec(specs_, detail::error_handler());
|
||||
return end;
|
||||
}
|
||||
|
||||
template <class FormatContext>
|
||||
@ -518,4 +573,12 @@ TEST(locale_test, sign) {
|
||||
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
|
||||
}
|
||||
|
||||
TEST(std_test_xchar, optional) {
|
||||
# ifdef __cpp_lib_optional
|
||||
EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
|
||||
EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}),
|
||||
L"optional(\"wide string\")");
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
Reference in New Issue
Block a user