Compare commits

..

No commits in common. "master" and "12.0.0" have entirely different histories.

39 changed files with 1162 additions and 1541 deletions

1
.github/FUNDING.yml vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,77 +1,3 @@
# 12.1.0 - 2025-10-29
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
gcc (https://github.com/fmtlib/fmt/issues/4573).
- Made `std::variant` and `std::expected` formatters work with `format_as`
(https://github.com/fmtlib/fmt/issues/4574,
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
- Made `fmt::join<string_view>` work with C++ modules
(https://github.com/fmtlib/fmt/issues/4379,
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
- Fixed a compatibility issue with C++ modules in clang
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
- Added support for cv-qualified types to the `std::optional` formatter
(https://github.com/fmtlib/fmt/issues/4561,
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
- Added demangling support (used in exception and `std::type_info` formatters)
for libc++ and clang-cl
(https://github.com/fmtlib/fmt/issues/4542,
https://github.com/fmtlib/fmt/pull/4560,
https://github.com/fmtlib/fmt/issues/4568,
https://github.com/fmtlib/fmt/pull/4571).
Thanks @FatihBAKIR and @rohitsutreja.
- Switched to global `malloc`/`free` to enable allocator customization
(https://github.com/fmtlib/fmt/issues/4569,
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
- Fixed compilation with locales disabled in the header-only mode
(https://github.com/fmtlib/fmt/issues/4550).
- Fixed compilation with clang 21 and `-std=c++20`
(https://github.com/fmtlib/fmt/issues/4552).
- Fixed a dynamic linking issue with clang-cl
(https://github.com/fmtlib/fmt/issues/4576,
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
- Fixed a warning suppression leakage on gcc
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
- Made more internal color APIs `constexpr`
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
- Fixed compatibility with clang as a host compiler for NVCC
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
- Fixed various warnings and lint issues
(https://github.com/fmtlib/fmt/issues/4565,
https://github.com/fmtlib/fmt/pull/4572,
https://github.com/fmtlib/fmt/pull/4557).
Thanks @LiangHuDream and @teruyamato0731.
- Improved documentation
(https://github.com/fmtlib/fmt/issues/4549,
https://github.com/fmtlib/fmt/pull/4551,
https://github.com/fmtlib/fmt/issues/4566,
https://github.com/fmtlib/fmt/pull/4567,
https://github.com/fmtlib/fmt/pull/4578,).
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
# 12.0.0 - 2025-09-17 # 12.0.0 - 2025-09-17
- Optimized the default floating point formatting - Optimized the default floating point formatting

View File

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

View File

@ -12,7 +12,7 @@
alternative to C stdio and C++ iostreams. alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to one of the funds If you like this project, please consider donating to one of the funds
that help victims of the war in Ukraine: <https://u24.gov.ua/>. that help victims of the war in Ukraine: <https://www.stopputin.net/>.
[Documentation](https://fmt.dev) [Documentation](https://fmt.dev)
@ -150,8 +150,8 @@ int main() {
} }
``` ```
This can be [up to 9 times faster than `fprintf`]( This can be [5 to 9 times faster than
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles** **Print with colors and text styles**
@ -178,17 +178,17 @@ Output on a modern terminal with Unicode support:
| Library | Method | Run Time, s | | Library | Method | Run Time, s |
|-------------------|---------------|-------------| |-------------------|---------------|-------------|
| libc | printf | 0.66 | | libc | printf | 0.91 |
| libc++ | std::ostream | 1.63 | | libc++ | std::ostream | 2.49 |
| {fmt} 12.1 | fmt::print | 0.44 | | {fmt} 9.1 | fmt::print | 0.74 |
| Boost Format 1.88 | boost::format | 3.89 | | Boost Format 1.80 | boost::format | 6.26 |
| Folly Format | folly::format | 1.28 | | Folly Format | folly::format | 1.87 |
{fmt} is the fastest of the benchmarked methods, \~50% faster than {fmt} is the fastest of the benchmarked methods, \~20% faster than
`printf`. `printf`.
The above results were generated by building `tinyformat_test.cpp` on The above results were generated by building `tinyformat_test.cpp` on
macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
taking the best of three runs. In the test, the format string taking the best of three runs. In the test, the format string
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000 `"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
times with output sent to `/dev/null`; for further details refer to the times with output sent to `/dev/null`; for further details refer to the
@ -217,11 +217,11 @@ in the following tables.
**Optimized build (-O3)** **Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | | Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------| |---------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 | | printf | 1.6 | 54 | 50 |
| IOStreams | 28.4 | 98 | 84 | | IOStreams | 25.9 | 98 | 84 |
| {fmt} `1122268` | 5.0 | 54 | 50 | | fmt 83652df | 4.8 | 54 | 50 |
| tinyformat | 32.6 | 164 | 136 | | tinyformat | 29.1 | 161 | 136 |
| Boost Format | 55.0 | 530 | 317 | | Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call {fmt} is fast to compile and is comparable to `printf` in terms of per-call
@ -230,12 +230,12 @@ binary size (within a rounding error on this system).
**Non-optimized build** **Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | | Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------| |---------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 | | printf | 1.4 | 54 | 50 |
| IOStreams | 27.0 | 88 | 68 | | IOStreams | 23.4 | 92 | 68 |
| {fmt} `1122268` | 4.7 | 87 | 84 | | {fmt} 83652df | 4.4 | 89 | 85 |
| tinyformat | 28.1 | 185 | 145 | | tinyformat | 24.5 | 204 | 161 |
| Boost Format | 38.9 | 678 | 381 | | Boost Format | 36.4 | 831 | 462 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries `libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a to compare formatting function overhead only. Boost Format is a

View File

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

View File

@ -321,6 +321,8 @@ parameterized version.
::: arg(const Char*, const T&) ::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Compatibility ### Compatibility
::: basic_string_view ::: basic_string_view
@ -622,8 +624,6 @@ Example:
::: ostream ::: ostream
::: output_file(cstring_view, T...)
::: windows_error ::: windows_error
<a id="ostream-api"></a> <a id="ostream-api"></a>
@ -706,55 +706,5 @@ following differences:
precision that provides round-trip guarantees similarly to other languages precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters `std::to_chars` which tries to generate the smallest number of characters
(ignoring redundant digits and sign in exponent) and may produce more (ignoring redundant digits and sign in exponent) and may procude more
decimal digits than necessary. decimal digits than necessary.
## Configuration Options
{fmt} provides configuration via CMake options and preprocessor macros to
enable or disable features and to optimize for binary size. For example, you
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
configuring CMake.
### CMake Options
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
Windows/MSVC. Unicode support is always enabled on other platforms.
### Macros
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
alternative to using the `fmt::fmt-header-only` CMake target.
Default: not defined.
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
Default: `1` (`0` if compiled with `-fno-exceptions`).
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
custom `fmt::assert_fail` function which is called on assertion failures and,
if exceptions are disabled, on runtime errors. Default: `0`.
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
arithmetic and string types other than `int`. This reduces library size at
the cost of per-call overhead. Default: `1`.
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
- `0` - off (default)
- `1` - disables locale support and applies some optimizations
- `2` - disables some Unicode features, named arguments and applies more
aggressive optimizations
### Binary Size Optimization
To minimize the binary footprint of {fmt} as much as possible at the cost of
some features, you can use the following configuration:
- CMake options:
- `FMT_OS=OFF`
- Macros:
- `FMT_BUILTIN_TYPES=0`
- `FMT_OPTIMIZE_SIZE=2`

View File

@ -20,7 +20,6 @@
margin-left: 1em; margin-left: 1em;
} }
code,
pre > code.decl { pre > code.decl {
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@ -76,7 +76,7 @@ hide:
<p> <p>
The default is <b>locale-independent</b>, but you can opt into localized The default is <b>locale-independent</b>, but you can opt into localized
formatting and {fmt} makes it work with Unicode, addressing issues in the formatting and {fmt} makes it work with Unicode, addressing issues in the
standard library. standard libary.
</p> </p>
</div> </div>

View File

@ -718,7 +718,7 @@ These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
Format specifications for range types have the following syntax: Format specifications for range types have the following syntax:
<pre><code class="language-json" <pre><code class="language-json"
>range_format_spec ::= ["n"][range_type][":" range_underlying_spec]</code> >range_format_spec ::= ["n"][range_type][range_underlying_spec]</code>
</pre> </pre>
The `'n'` option formats the range without the opening and closing brackets. The `'n'` option formats the range without the opening and closing brackets.
@ -761,16 +761,14 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [h, e, l, l, o] // Output: [h, e, l, l, o]
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'}); fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [104, 101, 108, 108, 111] // Output: [104, 101, 108, 108, 111]
fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e});
// Output: 3.141593, 2.718282
``` ```
## Format Examples ## Format Examples
This section contains examples of the format syntax and comparison with This section contains examples of the format syntax and comparison with
the `printf` formatting. the printf formatting.
In most of the cases the syntax is similar to the `printf` formatting, In most of the cases the syntax is similar to the printf formatting,
with the addition of the `{}` and with `:` used instead of `%`. For with the addition of the `{}` and with `:` used instead of `%`. For
example, `"%03.2f"` can be translated to `"{:03.2f}"`. example, `"%03.2f"` can be translated to `"{:03.2f}"`.

View File

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

View File

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

View File

@ -429,7 +429,7 @@ template <typename Char> struct ansi_color_escape {
private: private:
static constexpr size_t num_emphases = 8; static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases] = {}; Char buffer[7u + 4u * num_emphases];
size_t size = 0; size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,

View File

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

View File

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

View File

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

View File

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

View File

@ -18,13 +18,6 @@
#include "format.h" #include "format.h"
#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
# define FMT_LIFETIMEBOUND [[clang::lifetimebound]]
#else
# define FMT_LIFETIMEBOUND
#endif
FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address")
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT FMT_EXPORT
@ -241,6 +234,14 @@ using range_reference_type =
template <typename Range> template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>; using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename T> template <typename T>
struct range_format_kind_ struct range_format_kind_
: std::integral_constant<range_format, : std::integral_constant<range_format,
@ -820,12 +821,12 @@ auto join(Range&& r, string_view sep)
* *
* **Example**: * **Example**:
* *
* auto t = std::tuple<int, char>(1, 'a'); * auto t = std::tuple<int, char>{1, 'a'};
* fmt::print("{}", fmt::join(t, ", ")); * fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a * // Output: 1, a
*/ */
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<Tuple, char> { -> tuple_join_view<Tuple, char> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@ -84,13 +84,11 @@ namespace detail {
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p, auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) { const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
std::is_same_v<PathChar, wchar_t>) { return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf); else
} else {
return p.string<Char>(); return p.string<Char>();
} }
}
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
void write_escaped_path(basic_memory_buffer<Char>& quoted, void write_escaped_path(basic_memory_buffer<Char>& quoted,
@ -113,17 +111,12 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT #if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T>
template <typename Char, typename OutputIt, typename T, typename FormatContext> auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
-> OutputIt {
if constexpr (has_to_string_view<T>::value) if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v)); return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v); if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
formatter<std::remove_cv_t<T>, Char> underlying;
maybe_set_debug_format(underlying, true);
return underlying.format(v, ctx);
} }
#endif #endif
@ -146,8 +139,19 @@ template <typename Variant, typename Char> class is_variant_formattable {
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI #if FMT_USE_RTTI
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
char* begin) -> string_view { template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names. // Normalization of stdlib inline namespace names.
// libc++ inline namespaces. // libc++ inline namespaces.
// std::__1::* -> std::* // std::__1::* -> std::*
@ -156,12 +160,13 @@ inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
// std::__cxx11::* -> std::* // std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::* // std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) { if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std:: char* to = begin + 5; // std::
for (const char *from = to, *end = begin + demangled_name_view.size(); for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) { from < end;) {
// This is safe, because demangled_name is NUL-terminated. // This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') { if (from[0] == '_' && from[1] == '_') {
const char* next = from + 1; char* next = from + 1;
while (next < end && *next != ':') next++; while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') { if (next[0] == ':' && next[1] == ':') {
from = next + 2; from = next + 2;
@ -172,13 +177,12 @@ inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
} }
demangled_name_view = {begin, detail::to_unsigned(to - begin)}; demangled_name_view = {begin, detail::to_unsigned(to - begin)};
} }
return demangled_name_view; } else {
demangled_name_view = string_view(ti.name());
} }
return detail::write_bytes<char>(out, demangled_name_view);
template <class OutputIt> # elif FMT_MSC_VERSION
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out) const string_view demangled_name(ti.name());
-> OutputIt {
const string_view demangled_name(abi_name_view);
for (size_t i = 0; i < demangled_name.size(); ++i) { for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name; auto sub = demangled_name;
sub.remove_prefix(i); sub.remove_prefix(i);
@ -197,39 +201,6 @@ auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
if (*sub.begin() != ' ') *out++ = *sub.begin(); if (*sub.begin() != ' ') *out++ = *sub.begin();
} }
return out; return out;
}
template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = normalize_libcxx_inline_namespaces(
demangled_name_ptr.get(), demangled_name_ptr.get());
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
return normalize_msvc_abi_name(ti.name(), out);
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
const string_view demangled_name = ti.name();
std::string name_copy(demangled_name.size(), '\0');
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
// front of types
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
name_copy.end());
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
// environments, we need to eliminate both of them.
const string_view normalized_name =
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
return detail::write_bytes<char>(out, normalized_name);
# else # else
return detail::write_bytes<char>(out, string_view(ti.name())); return detail::write_bytes<char>(out, string_view(ti.name()));
# endif # endif
@ -284,6 +255,21 @@ template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
#if FMT_CPP_LIB_FILESYSTEM #if FMT_CPP_LIB_FILESYSTEM
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
template <typename Char> struct formatter<std::filesystem::path, Char> { template <typename Char> struct formatter<std::filesystem::path, Char> {
private: private:
format_specs specs_; format_specs specs_;
@ -333,21 +319,6 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
} }
}; };
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
template <size_t N, typename Char> template <size_t N, typename Char>
@ -382,16 +353,25 @@ template <typename T, typename Char>
struct formatter<std::optional<T>, Char, struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> { std::enable_if_t<is_formattable<T, Char>::value>> {
private: private:
formatter<std::remove_cv_t<T>, Char> underlying_; formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional = static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l', detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{}; '('>{};
static constexpr basic_string_view<Char> none = static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{}; detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public: public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
detail::maybe_set_debug_format(underlying_, true); maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx); return underlying_.parse(ctx);
} }
@ -427,10 +407,10 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) { if (value.has_value()) {
out = detail::write<Char>(out, "expected("); out = detail::write<Char>(out, "expected(");
if constexpr (!std::is_void<T>::value) if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value, ctx); out = detail::write_escaped_alternative<Char>(out, *value);
} else { } else {
out = detail::write<Char>(out, "unexpected("); out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx); out = detail::write_escaped_alternative<Char>(out, value.error());
} }
*out++ = ')'; *out++ = ')';
return out; return out;
@ -494,7 +474,7 @@ struct formatter<Variant, Char,
FMT_TRY { FMT_TRY {
std::visit( std::visit(
[&](const auto& v) { [&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v, ctx); out = detail::write_escaped_alternative<Char>(out, v);
}, },
value); value);
} }
@ -515,8 +495,6 @@ template <> struct formatter<std::error_code> {
bool debug_ = false; bool debug_ = false;
public: public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it == end) return it; if (it == end) return it;
@ -647,11 +625,6 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
}; };
#endif // __cpp_lib_atomic_flag_test #endif // __cpp_lib_atomic_flag_test
template <typename T> struct is_tuple_like;
template <typename T>
struct is_tuple_like<std::complex<T>> : std::false_type {};
template <typename T, typename Char> struct formatter<std::complex<T>, Char> { template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private: private:
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;

View File

@ -10,7 +10,7 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE #if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED! template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale; template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif #endif

View File

@ -2,10 +2,6 @@
# A script to invoke mkdocs with the correct environment. # A script to invoke mkdocs with the correct environment.
# Additionally supports deploying via mike: # Additionally supports deploying via mike:
# ./mkdocs deploy [mike-deploy-options] # ./mkdocs deploy [mike-deploy-options]
# For example:
# ./mkdocs deploy <version>
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
# <version> there.
import errno, os, shutil, sys import errno, os, shutil, sys
from subprocess import call from subprocess import call
@ -44,7 +40,7 @@ config_path = os.path.join(support_dir, 'mkdocs.yml')
args = sys.argv[1:] args = sys.argv[1:]
if len(args) > 0: if len(args) > 0:
command = args[0] command = args[0]
if command == 'deploy' or command == 'set-default': if command == 'deploy':
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:' git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
site_repo = git_url + 'fmtlib/fmt.dev.git' site_repo = git_url + 'fmtlib/fmt.dev.git'
@ -68,10 +64,6 @@ if len(args) > 0:
if ret != 0 or version == 'dev': if ret != 0 or version == 'dev':
sys.exit(ret) sys.exit(ret)
current_doc_path = os.path.join(site_dir, version) current_doc_path = os.path.join(site_dir, version)
# mike stages files added by deploy for deletion for unclear reason,
# undo it.
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
if False:
os.makedirs(current_doc_path, exist_ok=True) os.makedirs(current_doc_path, exist_ok=True)
redirect_page_path = os.path.join(current_doc_path, 'api.html') redirect_page_path = os.path.join(current_doc_path, 'api.html')
with open(redirect_page_path, "w") as file: with open(redirect_page_path, "w") as file:

View File

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

View File

@ -1,5 +1,7 @@
add_subdirectory(gtest) add_subdirectory(gtest)
include(CheckSymbolExists)
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
add_library(test-main STATIC ${TEST_MAIN_SRC}) add_library(test-main STATIC ${TEST_MAIN_SRC})
target_include_directories(test-main PUBLIC target_include_directories(test-main PUBLIC
@ -60,10 +62,21 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif () endif ()
add_fmt_test(ostream-test) add_fmt_test(ostream-test)
add_fmt_test(compile-test) add_fmt_test(compile-test)
add_fmt_test(compile-fp-test)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif()
add_fmt_test(printf-test) add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc) add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(no-builtin-types-test HEADER_ONLY) add_fmt_test(no-builtin-types-test HEADER_ONLY)
add_fmt_test(scan-test HEADER_ONLY) add_fmt_test(scan-test HEADER_ONLY)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
if (HAVE_STRPTIME)
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
endif ()
add_fmt_test(std-test) add_fmt_test(std-test)
try_compile(compile_result_unused try_compile(compile_result_unused
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}

62
test/compile-fp-test.cc Normal file
View File

@ -0,0 +1,62 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/compile.h"
#include "gmock/gmock.h"
#if FMT_USE_CONSTEVAL
template <size_t max_string_length, typename Char = char> struct test_string {
Char buffer[max_string_length] = {};
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif // FMT_USE_CONSTEVAL

View File

@ -90,6 +90,9 @@ TEST(compile_test, format_escape) {
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc")); EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
} }
TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) { TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@ -121,6 +124,7 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo", "true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f, fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable())); "foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
} }
TEST(compile_test, named) { TEST(compile_test, named) {
@ -129,6 +133,10 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled), static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>); fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar", EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"), fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar"))); fmt::arg("a1", "bar")));
@ -310,6 +318,7 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals; using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf)); EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42)); EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
} }
#endif #endif
@ -417,40 +426,6 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) { TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42)); EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
} }
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif #endif
#if FMT_USE_CONSTEXPR_STRING #if FMT_USE_CONSTEXPR_STRING

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,6 @@
#include <vector> #include <vector>
#include "fmt/os.h" // fmt::system_category #include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith #include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem #ifdef __cpp_lib_filesystem
@ -39,12 +38,13 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")), L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына"); "Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80"); EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD <20> TAIL");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")),
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")), "HEAD \xF0\x9F\x98\x80 TAIL");
"[\xED\xA0\xBD\xF0\x9F\x98\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")),
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\""); "HEAD <20>\xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
# endif # endif
} }
@ -145,7 +145,6 @@ TEST(std_test, optional) {
EXPECT_FALSE((fmt::is_formattable<unformattable>::value)); EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value)); EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value)); EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif #endif
} }
@ -197,33 +196,7 @@ class my_class {
return fmt::to_string(elm.av); return fmt::to_string(elm.av);
} }
}; };
class my_class_int {
public:
int av;
private:
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
};
} // namespace my_nso } // namespace my_nso
TEST(std_test, expected_format_as) {
#ifdef __cpp_lib_expected
EXPECT_EQ(
fmt::format(
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
"expected(\"first\")");
EXPECT_EQ(
fmt::format("{}",
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
"expected(\"7\")");
EXPECT_EQ(fmt::format("{}",
std::expected<my_nso::my_class_int, int>{
my_nso::my_class_int{8}}),
"expected(8)");
#endif
}
TEST(std_test, optional_format_as) { TEST(std_test, optional_format_as) {
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
@ -232,8 +205,6 @@ TEST(std_test, optional_format_as) {
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}), EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
"optional(\"7\")"); "optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif #endif
} }
@ -303,24 +274,6 @@ TEST(std_test, variant) {
#endif #endif
} }
TEST(std_test, variant_format_as) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
"variant(\"first\")");
EXPECT_EQ(fmt::format(
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
"variant(\"first\")");
EXPECT_EQ(
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
"variant(\"7\")");
EXPECT_EQ(
fmt::format("{}",
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
"variant(8)");
#endif
}
TEST(std_test, error_code) { TEST(std_test, error_code) {
auto& generic = std::generic_category(); auto& generic = std::generic_category();
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42"); EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
@ -335,10 +288,6 @@ TEST(std_test, error_code) {
EXPECT_EQ(fmt::format("{:s}", ec), ec.message()); EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)), EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
"\"generic:42\""); "\"generic:42\"");
EXPECT_EQ(fmt::format("{}",
std::map<std::error_code, int>{
{std::error_code(42, generic), 0}}),
"{\"generic:42\": 0}");
} }
template <typename Catch> void exception_test() { template <typename Catch> void exception_test() {