Merge remote-tracking branch 'zverovich/master'
Conflicts: include/fmt/core.h test/format-dyn-args-test.cc
This commit is contained in:
commit
baae799489
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
*.iml
|
||||
.idea/
|
||||
|
||||
@ -54,7 +54,7 @@ option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
|
||||
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT})
|
||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
|
||||
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||
project(FMT CXX)
|
||||
|
||||
# Get version from core.h
|
||||
@ -81,6 +81,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(JoinPaths)
|
||||
|
||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||
if (${index} GREATER -1)
|
||||
@ -173,7 +174,11 @@ endfunction()
|
||||
# Define the fmt library, its includes and the needed defines.
|
||||
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
|
||||
locale.h os.h ostream.h posix.h printf.h ranges.h)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
if (FMT_OS)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
else()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
endif ()
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
@ -201,8 +206,9 @@ set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
|
||||
|
||||
# Set FMT_LIB_NAME for pkg-config fmt.pc.
|
||||
get_target_property(FMT_LIB_NAME fmt OUTPUT_NAME)
|
||||
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
|
||||
# property because it's not set by default.
|
||||
set(FMT_LIB_NAME fmt)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
|
||||
endif ()
|
||||
@ -234,8 +240,8 @@ if (FMT_INSTALL)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||
"Installation directory for cmake files, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
"Installation directory for cmake files, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
||||
@ -247,22 +253,26 @@ if (FMT_INSTALL)
|
||||
endif ()
|
||||
|
||||
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
"Installation directory for libraries, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
|
||||
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS
|
||||
"Installation directory for include files, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING
|
||||
"Installation directory for include files, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
"Installation directory for pkgconfig (.pc) files, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
"Installation directory for pkgconfig (.pc) files, a relative path "
|
||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
${version_config}
|
||||
VERSION ${FMT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
|
||||
join_paths(libdir_for_pc_file "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}")
|
||||
join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
configure_file(
|
||||
"${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
|
||||
"${pkgconfig}"
|
||||
@ -307,6 +317,7 @@ endif ()
|
||||
# Control fuzzing independent of the unit tests.
|
||||
if (FMT_FUZZ)
|
||||
add_subdirectory(test/fuzzing)
|
||||
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
||||
endif ()
|
||||
|
||||
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||
|
||||
213
ChangeLog.rst
213
ChangeLog.rst
@ -1,3 +1,216 @@
|
||||
6.2.0 - 2020-04-05
|
||||
------------------
|
||||
|
||||
* Improved error reporting when trying to format an object of a non-formattable
|
||||
type:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::format("{}", S());
|
||||
|
||||
now gives::
|
||||
|
||||
include/fmt/core.h:1015:5: error: static_assert failed due to requirement
|
||||
'formattable' "Cannot format argument. To make type T formattable provide a
|
||||
formatter<T> specialization:
|
||||
https://fmt.dev/latest/api.html#formatting-user-defined-types"
|
||||
static_assert(
|
||||
^
|
||||
...
|
||||
note: in instantiation of function template specialization
|
||||
'fmt::v6::format<char [3], S, char>' requested here
|
||||
fmt::format("{}", S());
|
||||
^
|
||||
|
||||
if ``S`` is not formattable.
|
||||
|
||||
* Reduced library size by ~10%.
|
||||
|
||||
* Always print decimal point if ``#`` is specified
|
||||
(`#1476 <https://github.com/fmtlib/fmt/issues/1476>`_,
|
||||
`#1498 <https://github.com/fmtlib/fmt/issues/1498>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("{:#.0f}", 42.0);
|
||||
|
||||
now prints ``42.``
|
||||
|
||||
* Implemented the ``'L'`` specifier for locale-specific numeric formatting to
|
||||
improve compatibility with ``std::format``. The ``'n'`` specifier is now
|
||||
deprecated and will be removed in the next major release.
|
||||
|
||||
* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to
|
||||
``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically
|
||||
include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be
|
||||
disabled in the next major release.
|
||||
|
||||
* Added precision overflow detection in floating-point formatting.
|
||||
|
||||
* Implemented detection of invalid use of ``fmt::arg``.
|
||||
|
||||
* Used ``type_identity`` to block unnecessary template argument deduction.
|
||||
Thanks Tim Song.
|
||||
|
||||
* Improved UTF-8 handling
|
||||
(`#1109 <https://github.com/fmtlib/fmt/issues/1109>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("┌{0:─^{2}}┐\n"
|
||||
"│{1: ^{2}}│\n"
|
||||
"└{0:─^{2}}┘\n", "", "Привет, мир!", 20);
|
||||
|
||||
now prints::
|
||||
|
||||
┌────────────────────┐
|
||||
│ Привет, мир! │
|
||||
└────────────────────┘
|
||||
|
||||
on systems that support Unicode.
|
||||
|
||||
* Added experimental dynamic argument storage
|
||||
(`#1170 <https://github.com/fmtlib/fmt/issues/1170>`_,
|
||||
`#1584 <https://github.com/fmtlib/fmt/pull/1584>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back("answer");
|
||||
store.push_back(42);
|
||||
fmt::vprint("The {} is {}.\n", store);
|
||||
|
||||
prints::
|
||||
|
||||
The answer is 42.
|
||||
|
||||
Thanks `@vsolontsov-ll (Vladimir Solontsov)
|
||||
<https://github.com/vsolontsov-ll>`_.
|
||||
|
||||
* Made ``fmt::join`` accept ``initializer_list``
|
||||
(`#1591 <https://github.com/fmtlib/fmt/pull/1591>`_).
|
||||
Thanks `@Rapotkinnik (Nikolay Rapotkin) <https://github.com/Rapotkinnik>`_.
|
||||
|
||||
* Fixed handling of empty tuples
|
||||
(`#1588 <https://github.com/fmtlib/fmt/issues/1588>`_).
|
||||
|
||||
* Fixed handling of output iterators in ``format_to_n``
|
||||
(`#1506 <https://github.com/fmtlib/fmt/issues/1506>`_).
|
||||
|
||||
* Fixed formatting of ``std::chrono::duration`` types to wide output
|
||||
(`#1533 <https://github.com/fmtlib/fmt/pull/1533>`_).
|
||||
Thanks `@zeffy (pilao) <https://github.com/zeffy>`_.
|
||||
|
||||
* Added const ``begin`` and ``end`` overload to buffers
|
||||
(`#1553 <https://github.com/fmtlib/fmt/pull/1553>`_).
|
||||
Thanks `@dominicpoeschko <https://github.com/dominicpoeschko>`_.
|
||||
|
||||
* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``,
|
||||
``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely
|
||||
memory-constrained embedded system
|
||||
(`#1590 <https://github.com/fmtlib/fmt/pull/1590>`_).
|
||||
Thanks `@albaguirre (Alberto Aguirre) <https://github.com/albaguirre>`_.
|
||||
|
||||
* Made ``FMT_STRING`` work with ``constexpr`` ``string_view``
|
||||
(`#1589 <https://github.com/fmtlib/fmt/pull/1589>`_).
|
||||
Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_.
|
||||
|
||||
* Implemented a minor optimization in the format string parser
|
||||
(`#1560 <https://github.com/fmtlib/fmt/pull/1560>`_).
|
||||
Thanks `@IkarusDeveloper <https://github.com/IkarusDeveloper>`_.
|
||||
|
||||
* Improved attribute detection
|
||||
(`#1469 <https://github.com/fmtlib/fmt/pull/1469>`_,
|
||||
`#1475 <https://github.com/fmtlib/fmt/pull/1475>`_,
|
||||
`#1576 <https://github.com/fmtlib/fmt/pull/1576>`_).
|
||||
Thanks `@federico-busato (Federico) <https://github.com/federico-busato>`_,
|
||||
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||
`@refnum <https://github.com/refnum>`_.
|
||||
|
||||
* Improved documentation
|
||||
(`#1481 <https://github.com/fmtlib/fmt/pull/1481>`_,
|
||||
`#1523 <https://github.com/fmtlib/fmt/pull/1523>`_).
|
||||
Thanks `@JackBoosY (Jack·Boos·Yu) <https://github.com/JackBoosY>`_,
|
||||
`@imba-tjd (谭九鼎) <https://github.com/imba-tjd>`_.
|
||||
|
||||
* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden``
|
||||
(`#1535 <https://github.com/fmtlib/fmt/pull/1535>`_).
|
||||
Thanks `@milianw (Milian Wolff) <https://github.com/milianw>`_.
|
||||
|
||||
* Implemented various build configuration fixes and improvements
|
||||
(`#1264 <https://github.com/fmtlib/fmt/issues/1264>`_,
|
||||
`#1460 <https://github.com/fmtlib/fmt/issues/1460>`_,
|
||||
`#1534 <https://github.com/fmtlib/fmt/pull/1534>`_,
|
||||
`#1536 <https://github.com/fmtlib/fmt/issues/1536>`_,
|
||||
`#1545 <https://github.com/fmtlib/fmt/issues/1545>`_,
|
||||
`#1546 <https://github.com/fmtlib/fmt/pull/1546>`_,
|
||||
`#1566 <https://github.com/fmtlib/fmt/issues/1566>`_,
|
||||
`#1582 <https://github.com/fmtlib/fmt/pull/1582>`_,
|
||||
`#1597 <https://github.com/fmtlib/fmt/issues/1597>`_,
|
||||
`#1598 <https://github.com/fmtlib/fmt/pull/1598>`_).
|
||||
Thanks `@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_,
|
||||
`@jwillikers (Jordan Williams) <https://github.com/jwillikers>`_,
|
||||
`@stac47 (Laurent Stacul) <https://github.com/stac47>`_.
|
||||
|
||||
* Fixed various warnings and compilation issues
|
||||
(`#1433 <https://github.com/fmtlib/fmt/pull/1433>`_,
|
||||
`#1461 <https://github.com/fmtlib/fmt/issues/1461>`_,
|
||||
`#1470 <https://github.com/fmtlib/fmt/pull/1470>`_,
|
||||
`#1480 <https://github.com/fmtlib/fmt/pull/1480>`_,
|
||||
`#1485 <https://github.com/fmtlib/fmt/pull/1485>`_,
|
||||
`#1492 <https://github.com/fmtlib/fmt/pull/1492>`_,
|
||||
`#1493 <https://github.com/fmtlib/fmt/issues/1493>`_,
|
||||
`#1504 <https://github.com/fmtlib/fmt/issues/1504>`_,
|
||||
`#1505 <https://github.com/fmtlib/fmt/pull/1505>`_,
|
||||
`#1512 <https://github.com/fmtlib/fmt/pull/1512>`_,
|
||||
`#1515 <https://github.com/fmtlib/fmt/issues/1515>`_,
|
||||
`#1516 <https://github.com/fmtlib/fmt/pull/1516>`_,
|
||||
`#1518 <https://github.com/fmtlib/fmt/pull/1518>`_,
|
||||
`#1519 <https://github.com/fmtlib/fmt/pull/1519>`_,
|
||||
`#1520 <https://github.com/fmtlib/fmt/pull/1520>`_,
|
||||
`#1521 <https://github.com/fmtlib/fmt/pull/1521>`_,
|
||||
`#1522 <https://github.com/fmtlib/fmt/pull/1522>`_,
|
||||
`#1524 <https://github.com/fmtlib/fmt/issues/1524>`_,
|
||||
`#1530 <https://github.com/fmtlib/fmt/pull/1530>`_,
|
||||
`#1531 <https://github.com/fmtlib/fmt/issues/1531>`_,
|
||||
`#1532 <https://github.com/fmtlib/fmt/pull/1532>`_,
|
||||
`#1539 <https://github.com/fmtlib/fmt/issues/1539>`_,
|
||||
`#1547 <https://github.com/fmtlib/fmt/issues/1547>`_,
|
||||
`#1548 <https://github.com/fmtlib/fmt/issues/1548>`_,
|
||||
`#1554 <https://github.com/fmtlib/fmt/pull/1554>`_,
|
||||
`#1567 <https://github.com/fmtlib/fmt/issues/1567>`_,
|
||||
`#1568 <https://github.com/fmtlib/fmt/pull/1568>`_,
|
||||
`#1569 <https://github.com/fmtlib/fmt/pull/1569>`_,
|
||||
`#1571 <https://github.com/fmtlib/fmt/pull/1571>`_,
|
||||
`#1573 <https://github.com/fmtlib/fmt/pull/1573>`_,
|
||||
`#1575 <https://github.com/fmtlib/fmt/pull/1575>`_,
|
||||
`#1581 <https://github.com/fmtlib/fmt/pull/1581>`_,
|
||||
`#1583 <https://github.com/fmtlib/fmt/issues/1583>`_,
|
||||
`#1586 <https://github.com/fmtlib/fmt/issues/1586>`_,
|
||||
`#1587 <https://github.com/fmtlib/fmt/issues/1587>`_,
|
||||
`#1594 <https://github.com/fmtlib/fmt/issues/1594>`_,
|
||||
`#1596 <https://github.com/fmtlib/fmt/pull/1596>`_,
|
||||
`#1604 <https://github.com/fmtlib/fmt/issues/1604>`_,
|
||||
`#1606 <https://github.com/fmtlib/fmt/pull/1606>`_,
|
||||
`#1607 <https://github.com/fmtlib/fmt/issues/1607>`_,
|
||||
`#1609 <https://github.com/fmtlib/fmt/issues/1609>`_).
|
||||
Thanks `@marti4d (Chris Martin) <https://github.com/marti4d>`_,
|
||||
`@iPherian <https://github.com/iPherian>`_,
|
||||
`@parkertomatoes <https://github.com/parkertomatoes>`_,
|
||||
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
|
||||
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||
`@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
|
||||
`@torsten48 <https://github.com/torsten48>`_,
|
||||
`@tohammer (Tobias Hammer) <https://github.com/tohammer>`_,
|
||||
`@lefticus (Jason Turner) <https://github.com/lefticus>`_,
|
||||
`@ryusakki (Haise) <https://github.com/ryusakki>`_,
|
||||
`@adnsv (Alex Denisov) <https://github.com/adnsv>`_,
|
||||
`@fghzxm <https://github.com/fghzxm>`_,
|
||||
`@refnum <https://github.com/refnum>`_,
|
||||
`@pramodk (Pramod Kumbhar) <https://github.com/pramodk>`_,
|
||||
`@Spirrwell <https://github.com/Spirrwell>`_,
|
||||
`@scramsby (Scott Ramsby) <https://github.com/scramsby>`_.
|
||||
|
||||
6.1.2 - 2019-12-11
|
||||
------------------
|
||||
|
||||
|
||||
41
README.rst
41
README.rst
@ -264,8 +264,8 @@ or the bloat test::
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform real-time
|
||||
strategy game
|
||||
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform
|
||||
real-time strategy game
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
@ -282,6 +282,16 @@ Projects using this library
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
* `Drake <https://drake.mit.edu/>`_: A planning, control, and analysis toolbox
|
||||
for nonlinear dynamical systems (MIT)
|
||||
|
||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||
(Lyft)
|
||||
|
||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||
|
||||
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
@ -293,25 +303,20 @@ Projects using this library
|
||||
|
||||
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
|
||||
|
||||
* `Drake <https://drake.mit.edu/>`_: A planning, control, and analysis toolbox
|
||||
for nonlinear dynamical systems (MIT)
|
||||
|
||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||
(Lyft)
|
||||
|
||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||
|
||||
* `MongoDB <https://mongodb.com/>`_: Distributed document database
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `OpenSpace <https://openspaceproject.com/>`_: An open-source astrovisualization
|
||||
framework
|
||||
* `OpenSpace <https://openspaceproject.com/>`_: An open-source
|
||||
astrovisualization framework
|
||||
|
||||
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
||||
An MMO server, compatible with most Ultima Online clients
|
||||
|
||||
* `PyTorch <https://github.com/pytorch/pytorch>`_: An open-source machine
|
||||
learning library
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
|
||||
associative database
|
||||
|
||||
@ -320,13 +325,14 @@ Projects using this library
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
|
||||
proxy
|
||||
|
||||
* `redpanda <https://vectorized.io/redpanda>`_: A 10x faster Kafka® replacement
|
||||
for mission critical systems written in C++
|
||||
|
||||
* `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client
|
||||
library
|
||||
|
||||
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:
|
||||
Small crossplatform 2D graphic engine
|
||||
|
||||
* `Salesforce Analytics Cloud <https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
* `Salesforce Analytics Cloud
|
||||
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `Scylla <https://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store
|
||||
@ -344,6 +350,9 @@ Projects using this library
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
||||
MMORPG framework
|
||||
|
||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: The new Windows
|
||||
Terminal
|
||||
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
|
||||
21
doc/api.rst
21
doc/api.rst
@ -9,7 +9,8 @@ The {fmt} library API consists of the following parts:
|
||||
* :ref:`fmt/core.h <core-api>`: the core API providing argument handling
|
||||
facilities and a lightweight subset of formatting functions
|
||||
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
|
||||
format string checks, output iterator and user-defined type support
|
||||
format string checks, wide string, output iterator and user-defined type
|
||||
support
|
||||
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
|
||||
and tuples
|
||||
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
||||
@ -43,7 +44,7 @@ participate in an overload resolution if the latter is not a string.
|
||||
.. _format:
|
||||
|
||||
.. doxygenfunction:: format(const S&, Args&&...)
|
||||
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<Char>>)
|
||||
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<type_identity_t<Char>>>)
|
||||
|
||||
.. _print:
|
||||
|
||||
@ -104,7 +105,7 @@ Format API
|
||||
==========
|
||||
|
||||
``fmt/format.h`` defines the full format API providing compile-time format
|
||||
string checks, output iterator and user-defined type support.
|
||||
string checks, wide string, output iterator and user-defined type support.
|
||||
|
||||
Compile-time Format String Checks
|
||||
---------------------------------
|
||||
@ -178,8 +179,7 @@ example::
|
||||
|
||||
enum class color {red, green, blue};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<color>: formatter<string_view> {
|
||||
template <> struct fmt::formatter<color>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(color c, FormatContext& ctx) {
|
||||
@ -193,6 +193,15 @@ example::
|
||||
}
|
||||
};
|
||||
|
||||
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
|
||||
all string format specifications, for example
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
fmt::format("{:>10}", color::blue)
|
||||
|
||||
will return ``" blue"``.
|
||||
|
||||
You can also write a formatter for a hierarchy of classes::
|
||||
|
||||
#include <type_traits>
|
||||
@ -399,7 +408,7 @@ formatting::
|
||||
|
||||
std::time_t t = std::time(nullptr);
|
||||
// Prints "The date is 2016-04-29." (with the current date)
|
||||
fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t));
|
||||
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
||||
|
||||
The format string syntax is described in the documentation of
|
||||
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
|
||||
|
||||
@ -6,7 +6,7 @@ import errno, os, shutil, sys, tempfile
|
||||
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2']
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0']
|
||||
|
||||
def pip_install(package, commit=None, **kwargs):
|
||||
"Install package using pip."
|
||||
|
||||
@ -81,8 +81,8 @@ The general form of a *standard format specifier* is:
|
||||
sign: "+" | "-" | " "
|
||||
width: `integer` | "{" `arg_id` "}"
|
||||
precision: `integer` | "{" `arg_id` "}"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
||||
|
||||
The *fill* character can be any Unicode code point other than ``'{'`` or
|
||||
``'}'``. The presence of a fill character is signaled by the character following
|
||||
@ -143,7 +143,7 @@ conversions, trailing zeros are not removed from the result.
|
||||
.. ifconfig:: False
|
||||
|
||||
The ``','`` option signals the use of a comma for a thousands separator.
|
||||
For a locale aware separator, use the ``'n'`` integer presentation type
|
||||
For a locale aware separator, use the ``'L'`` integer presentation type
|
||||
instead.
|
||||
|
||||
*width* is a decimal integer defining the minimum field width. If not
|
||||
@ -214,9 +214,9 @@ The available integer presentation types are:
|
||||
| | ``'#'`` option with this type adds the prefix ``"0X"`` |
|
||||
| | to the output value. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'n'`` | Number. This is the same as ``'d'``, except that it uses |
|
||||
| | the current locale setting to insert the appropriate |
|
||||
| | number separator characters. |
|
||||
| ``'L'`` | Locale-specific format. This is the same as ``'d'``, |
|
||||
| | except that it uses the current locale setting to insert |
|
||||
| | the appropriate number separator characters. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| none | The same as ``'d'``. |
|
||||
+---------+----------------------------------------------------------+
|
||||
@ -261,9 +261,9 @@ The available presentation types for floating-point values are:
|
||||
| | ``'E'`` if the number gets too large. The |
|
||||
| | representations of infinity and NaN are uppercased, too. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'n'`` | Number. This is the same as ``'g'``, except that it uses |
|
||||
| | the current locale setting to insert the appropriate |
|
||||
| | number separator characters. |
|
||||
| ``'L'`` | Locale-specific format. This is the same as ``'g'``, |
|
||||
| | except that it uses the current locale setting to insert |
|
||||
| | the appropriate number separator characters. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| none | Similar to ``'g'``, except that fixed-point notation, |
|
||||
| | when used, has at least one digit past the decimal |
|
||||
@ -403,7 +403,7 @@ Using the comma as a thousands separator::
|
||||
|
||||
#include <fmt/locale.h>
|
||||
|
||||
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:n}", 1234567890);
|
||||
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
|
||||
// s == "1,234,567,890"
|
||||
|
||||
.. ifconfig:: False
|
||||
|
||||
@ -768,17 +768,23 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
|
||||
val);
|
||||
}
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
||||
return std::copy(unit.begin(), unit.end(), out);
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
|
||||
// This works when wchar_t is UTF-32 because units only contain characters
|
||||
// that have the same representation in UTF-16 and UTF-32.
|
||||
utf8_to_utf16 u(unit);
|
||||
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename Period, typename OutputIt>
|
||||
OutputIt format_duration_unit(OutputIt out) {
|
||||
if (const char* unit = get_units<Period>()) {
|
||||
string_view s(unit);
|
||||
if (const_check(std::is_same<Char, wchar_t>())) {
|
||||
utf8_to_utf16 u(s);
|
||||
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
||||
}
|
||||
return std::copy(s.begin(), s.end(), out);
|
||||
}
|
||||
if (const char* unit = get_units<Period>())
|
||||
return copy_unit(string_view(unit), out, Char());
|
||||
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
|
||||
if (Period::den == 1) return format_to(out, num_f, Period::num);
|
||||
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
|
||||
|
||||
@ -412,7 +412,7 @@ template <typename Char> struct ansi_color_escape {
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::strlen(buffer);
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -576,7 +576,9 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
|
||||
FMT_ENABLE_IF(
|
||||
internal::is_output_iterator<OutputIt>::value&& std::is_base_of<
|
||||
internal::basic_compiled_format, CompiledFormat>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -22,8 +22,14 @@
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# if defined(NOMINMAX)
|
||||
# include <windows.h>
|
||||
# else
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
# undef NOMINMAX
|
||||
# endif
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@ -317,6 +323,8 @@ const char basic_data<T>::background_color[] = "\x1b[48;2;";
|
||||
template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m";
|
||||
template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m";
|
||||
template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '};
|
||||
template <typename T>
|
||||
const char basic_data<T>::padding_shifts[] = {31, 31, 0, 1, 0};
|
||||
|
||||
template <typename T> struct bits {
|
||||
static FMT_CONSTEXPR_DECL const int value =
|
||||
@ -507,20 +515,23 @@ class bigint {
|
||||
basic_memory_buffer<bigit, bigits_capacity> bigits_;
|
||||
int exp_;
|
||||
|
||||
bigit operator[](int index) const { return bigits_[to_unsigned(index)]; }
|
||||
bigit& operator[](int index) { return bigits_[to_unsigned(index)]; }
|
||||
|
||||
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
|
||||
|
||||
friend struct formatter<bigint>;
|
||||
|
||||
void subtract_bigits(int index, bigit other, bigit& borrow) {
|
||||
auto result = static_cast<double_bigit>(bigits_[index]) - other - borrow;
|
||||
bigits_[index] = static_cast<bigit>(result);
|
||||
auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
|
||||
(*this)[index] = static_cast<bigit>(result);
|
||||
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
|
||||
}
|
||||
|
||||
void remove_leading_zeros() {
|
||||
int num_bigits = static_cast<int>(bigits_.size()) - 1;
|
||||
while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits;
|
||||
bigits_.resize(num_bigits + 1);
|
||||
while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
|
||||
bigits_.resize(to_unsigned(num_bigits + 1));
|
||||
}
|
||||
|
||||
// Computes *this -= other assuming aligned bigints and *this >= other.
|
||||
@ -591,7 +602,7 @@ class bigint {
|
||||
|
||||
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
|
||||
|
||||
bigint& operator<<=(int shift) {
|
||||
FMT_NOINLINE bigint& operator<<=(int shift) {
|
||||
assert(shift >= 0);
|
||||
exp_ += shift / bigit_bits;
|
||||
shift %= bigit_bits;
|
||||
@ -621,7 +632,7 @@ class bigint {
|
||||
int end = i - j;
|
||||
if (end < 0) end = 0;
|
||||
for (; i >= end; --i, --j) {
|
||||
bigit lhs_bigit = lhs.bigits_[i], rhs_bigit = rhs.bigits_[j];
|
||||
bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j];
|
||||
if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1;
|
||||
}
|
||||
if (i != j) return i > j ? 1 : -1;
|
||||
@ -636,7 +647,7 @@ class bigint {
|
||||
if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
|
||||
if (max_lhs_bigits > num_rhs_bigits) return 1;
|
||||
auto get_bigit = [](const bigint& n, int i) -> bigit {
|
||||
return i >= n.exp_ && i < n.num_bigits() ? n.bigits_[i - n.exp_] : 0;
|
||||
return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0;
|
||||
};
|
||||
double_bigit borrow = 0;
|
||||
int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_);
|
||||
@ -676,7 +687,7 @@ class bigint {
|
||||
basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
|
||||
int num_bigits = static_cast<int>(bigits_.size());
|
||||
int num_result_bigits = 2 * num_bigits;
|
||||
bigits_.resize(num_result_bigits);
|
||||
bigits_.resize(to_unsigned(num_result_bigits));
|
||||
using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>;
|
||||
auto sum = accumulator_t();
|
||||
for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) {
|
||||
@ -686,7 +697,7 @@ class bigint {
|
||||
// Most terms are multiplied twice which can be optimized in the future.
|
||||
sum += static_cast<double_bigit>(n[i]) * n[j];
|
||||
}
|
||||
bigits_[bigit_index] = static_cast<bigit>(sum);
|
||||
(*this)[bigit_index] = static_cast<bigit>(sum);
|
||||
sum >>= bits<bigit>::value; // Compute the carry.
|
||||
}
|
||||
// Do the same for the top half.
|
||||
@ -694,7 +705,7 @@ class bigint {
|
||||
++bigit_index) {
|
||||
for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;)
|
||||
sum += static_cast<double_bigit>(n[i++]) * n[j--];
|
||||
bigits_[bigit_index] = static_cast<bigit>(sum);
|
||||
(*this)[bigit_index] = static_cast<bigit>(sum);
|
||||
sum >>= bits<bigit>::value;
|
||||
}
|
||||
--num_result_bigits;
|
||||
@ -708,11 +719,11 @@ class bigint {
|
||||
FMT_ASSERT(this != &divisor, "");
|
||||
if (compare(*this, divisor) < 0) return 0;
|
||||
int num_bigits = static_cast<int>(bigits_.size());
|
||||
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1] != 0, "");
|
||||
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
|
||||
int exp_difference = exp_ - divisor.exp_;
|
||||
if (exp_difference > 0) {
|
||||
// Align bigints by adding trailing zeros to simplify subtraction.
|
||||
bigits_.resize(num_bigits + exp_difference);
|
||||
bigits_.resize(to_unsigned(num_bigits + exp_difference));
|
||||
for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
|
||||
bigits_[j] = bigits_[i];
|
||||
std::uninitialized_fill_n(bigits_.data(), exp_difference, 0);
|
||||
@ -1023,7 +1034,7 @@ void fallback_format(Double d, buffer<char>& buf, int& exp10) {
|
||||
if (result > 0 || (result == 0 && (digit % 2) != 0))
|
||||
++data[num_digits - 1];
|
||||
}
|
||||
buf.resize(num_digits);
|
||||
buf.resize(to_unsigned(num_digits));
|
||||
exp10 -= num_digits - 1;
|
||||
return;
|
||||
}
|
||||
@ -1142,7 +1153,7 @@ int snprintf_float(T value, int precision, float_specs specs,
|
||||
for (;;) {
|
||||
auto begin = buf.data() + offset;
|
||||
auto capacity = buf.capacity() - offset;
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
@ -1157,7 +1168,7 @@ int snprintf_float(T value, int precision, float_specs specs,
|
||||
buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially.
|
||||
continue;
|
||||
}
|
||||
unsigned size = to_unsigned(result);
|
||||
auto size = to_unsigned(result);
|
||||
// Size equal to capacity means that the last character was truncated.
|
||||
if (size >= capacity) {
|
||||
buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'.
|
||||
@ -1175,7 +1186,7 @@ int snprintf_float(T value, int precision, float_specs specs,
|
||||
--p;
|
||||
} while (is_digit(*p));
|
||||
int fraction_size = static_cast<int>(end - p - 1);
|
||||
std::memmove(p, p + 1, fraction_size);
|
||||
std::memmove(p, p + 1, to_unsigned(fraction_size));
|
||||
buf.resize(size - 1);
|
||||
return -fraction_size;
|
||||
}
|
||||
@ -1204,9 +1215,9 @@ int snprintf_float(T value, int precision, float_specs specs,
|
||||
while (*fraction_end == '0') --fraction_end;
|
||||
// Move the fractional part left to get rid of the decimal point.
|
||||
fraction_size = static_cast<int>(fraction_end - begin - 1);
|
||||
std::memmove(begin + 1, begin + 2, fraction_size);
|
||||
std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size));
|
||||
}
|
||||
buf.resize(fraction_size + offset + 1);
|
||||
buf.resize(to_unsigned(fraction_size) + offset + 1);
|
||||
return exp - fraction_size;
|
||||
}
|
||||
}
|
||||
@ -1277,7 +1288,7 @@ template <> struct formatter<internal::bigint> {
|
||||
auto out = ctx.out();
|
||||
bool first = true;
|
||||
for (auto i = n.bigits_.size(); i > 0; --i) {
|
||||
auto value = n.bigits_[i - 1];
|
||||
auto value = n.bigits_[i - 1u];
|
||||
if (first) {
|
||||
out = format_to(out, "{:x}", value);
|
||||
first = false;
|
||||
@ -1313,7 +1324,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
}
|
||||
if (auto num_chars_left = s.data() + s.size() - p) {
|
||||
char buf[2 * block_size - 1] = {};
|
||||
memcpy(buf, p, num_chars_left);
|
||||
memcpy(buf, p, to_unsigned(num_chars_left));
|
||||
p = buf;
|
||||
do {
|
||||
p = transcode(p);
|
||||
@ -1368,7 +1379,7 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
|
||||
if (!WriteConsoleW(reinterpret_cast<HANDLE>(_get_osfhandle(fd)),
|
||||
u16.c_str(), static_cast<DWORD>(u16.size()), &written,
|
||||
nullptr)) {
|
||||
throw format_error("failed to write to console");
|
||||
FMT_THROW(format_error("failed to write to console"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -15,11 +15,10 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <clocale> // for locale_t
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // for strtod_l
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
@ -346,21 +345,15 @@ long getpagesize();
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class Locale {
|
||||
class locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||
|
||||
static locale_t newlocale(int category_mask, const char* locale, locale_t) {
|
||||
return _create_locale(category_mask, locale);
|
||||
}
|
||||
|
||||
static void freelocale(locale_t locale) { _free_locale(locale); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
return _strtod_l(nptr, endptr, locale);
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||
return _strtod_l(nptr, endptr, loc);
|
||||
}
|
||||
# endif
|
||||
|
||||
@ -368,13 +361,18 @@ class Locale {
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
Locale(const Locale&) = delete;
|
||||
void operator=(const Locale&) = delete;
|
||||
locale(const locale&) = delete;
|
||||
void operator=(const locale&) = delete;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) {
|
||||
locale() {
|
||||
# ifndef _WIN32
|
||||
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||
# else
|
||||
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||
# endif
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~Locale() { freelocale(locale_); }
|
||||
~locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
@ -387,6 +385,7 @@ class Locale {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
||||
@ -9,9 +9,14 @@
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
@ -93,9 +98,9 @@ void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
#endif
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
output << value;
|
||||
buf.resize(buf.size());
|
||||
@ -104,14 +109,32 @@ void format_value(buffer<Char>& buf, const T& value,
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: formatter<basic_string_view<Char>, Char> {
|
||||
template <typename Context>
|
||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
auto parse(basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
|
||||
@ -141,6 +141,13 @@ template <typename Context> class char_converter {
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
@ -172,23 +179,27 @@ template <typename Char> class printf_width_handler {
|
||||
};
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void printf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename Context>
|
||||
internal::truncating_iterator<OutputIt> printf(
|
||||
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return Context(it, format, args).format();
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
using internal::printf; // For printing into memory_buffer.
|
||||
// For printing into memory_buffer.
|
||||
template <typename Char, typename Context>
|
||||
FMT_DEPRECATED void printf(internal::buffer<Char>& buf,
|
||||
basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return internal::vprintf(buf, format, args);
|
||||
}
|
||||
using internal::vprintf;
|
||||
|
||||
template <typename Range> class printf_arg_formatter;
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
/**
|
||||
@ -324,6 +335,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
using char_type = Char;
|
||||
using iterator = OutputIt;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
private:
|
||||
@ -331,7 +343,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
basic_format_parse_context<Char> parse_ctx_;
|
||||
parse_context_type parse_ctx_;
|
||||
|
||||
static void parse_flags(format_specs& specs, const Char*& it,
|
||||
const Char* end);
|
||||
@ -362,7 +374,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
basic_format_parse_context<Char>& parse_context() { return parse_ctx_; }
|
||||
parse_context_type& parse_context() { return parse_ctx_; }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
parse_ctx_.on_error(message);
|
||||
@ -471,7 +483,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument index out of range");
|
||||
if (arg_index == 0) on_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
@ -490,6 +502,13 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(arg_index);
|
||||
if (specs.precision >= 0 && arg.type() == internal::type::cstring_type) {
|
||||
auto str = visit_format_arg(internal::get_cstring<Char>(), arg);
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = internal::make_arg<basic_printf_context>(basic_string_view<Char>(
|
||||
str, nul != str_end ? nul - str : specs.precision));
|
||||
}
|
||||
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
@ -605,7 +624,7 @@ inline std::basic_string<Char> vsprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
@ -630,7 +649,7 @@ inline int vfprintf(
|
||||
std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
std::size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
@ -683,7 +702,7 @@ inline int vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
@ -12,7 +12,9 @@
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// output only up to N items from the range.
|
||||
@ -104,10 +106,7 @@ struct is_range_<
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype(std::tuple_size<U>::value,
|
||||
(void)std::declval<typename std::tuple_element<0, U>::type>(),
|
||||
int());
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
@ -360,6 +359,29 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, char> join(
|
||||
std::initializer_list<T> list, string_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, wchar_t> join(
|
||||
std::initializer_list<T> list, wstring_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
||||
@ -13,7 +13,7 @@ namespace internal {
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
@ -29,7 +29,7 @@ struct sprintf_specs {
|
||||
bool alt : 1;
|
||||
|
||||
template <typename Char>
|
||||
constexpr sprintf_specs(basic_format_specs<Char> specs)
|
||||
constexpr explicit sprintf_specs(basic_format_specs<Char> specs)
|
||||
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
|
||||
|
||||
constexpr bool has_precision() const { return precision >= 0; }
|
||||
@ -81,7 +81,8 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
|
||||
unsigned n = internal::to_unsigned(result);
|
||||
if (n < buf.capacity()) {
|
||||
// Find the decimal point.
|
||||
auto p = buf.data(), end = p + n;
|
||||
auto* p = buf.data();
|
||||
auto* end = p + n;
|
||||
if (*p == '+' || *p == '-') ++p;
|
||||
if (specs.type != 'a' && specs.type != 'A') {
|
||||
while (p < end && *p >= '0' && *p <= '9') ++p;
|
||||
@ -113,6 +114,46 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
|
||||
}
|
||||
return decimal_point_pos;
|
||||
}
|
||||
|
||||
// DEPRECATED.
|
||||
template <typename Context> class arg_map {
|
||||
private:
|
||||
struct entry {
|
||||
basic_string_view<typename Context::char_type> name;
|
||||
basic_format_arg<Context> arg;
|
||||
};
|
||||
|
||||
entry* map_;
|
||||
unsigned size_;
|
||||
|
||||
void push_back(value<Context> val) {
|
||||
const auto& named = *val.named_arg;
|
||||
map_[size_] = {named.name, named.template deserialize<Context>()};
|
||||
++size_;
|
||||
}
|
||||
|
||||
public:
|
||||
void init(const basic_format_args<Context>& args);
|
||||
};
|
||||
|
||||
// This is deprecated and is kept only to preserve ABI compatibility.
|
||||
template <typename Context>
|
||||
void arg_map<Context>::init(const basic_format_args<Context>& args) {
|
||||
if (map_) return;
|
||||
map_ = new entry[internal::to_unsigned(args.max_size())];
|
||||
if (args.is_packed()) {
|
||||
for (int i = 0;; ++i) {
|
||||
internal::type arg_type = args.type(i);
|
||||
if (arg_type == internal::type::none_type) return;
|
||||
if (arg_type == internal::type::named_arg_type)
|
||||
push_back(args.values_[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0, n = args.max_size(); i < n; ++i) {
|
||||
auto type = args.args_[i].type_;
|
||||
if (type == internal::type::named_arg_type) push_back(args.args_[i].value_);
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
|
||||
@ -170,7 +211,4 @@ template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template FMT_API std::wstring internal::vformat<wchar_t>(
|
||||
wstring_view, basic_format_args<wformat_context>);
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
26
support/cmake/JoinPaths.cmake
Normal file
26
support/cmake/JoinPaths.cmake
Normal file
@ -0,0 +1,26 @@
|
||||
# This module provides function for joining paths
|
||||
# known from from most languages
|
||||
#
|
||||
# Original license:
|
||||
# SPDX-License-Identifier: (MIT OR CC0-1.0)
|
||||
# Explicit permission given to distribute this module under
|
||||
# the terms of the project as described in /LICENSE.rst.
|
||||
# Copyright 2020 Jan Tojnar
|
||||
# https://github.com/jtojnar/cmake-snips
|
||||
#
|
||||
# Modelled after Python’s os.path.join
|
||||
# https://docs.python.org/3.7/library/os.path.html#os.path.join
|
||||
# Windows not supported
|
||||
function(join_paths joined_path first_path_segment)
|
||||
set(temp_path "${first_path_segment}")
|
||||
foreach(current_segment IN LISTS ARGN)
|
||||
if(NOT ("${current_segment}" STREQUAL ""))
|
||||
if(IS_ABSOLUTE "${current_segment}")
|
||||
set(temp_path "${current_segment}")
|
||||
else()
|
||||
set(temp_path "${temp_path}/${current_segment}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
set(${joined_path} "${temp_path}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
@ -1,7 +1,7 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
libdir=@libdir_for_pc_file@
|
||||
includedir=@includedir_for_pc_file@
|
||||
|
||||
Name: fmt
|
||||
Description: A modern formatting library
|
||||
|
||||
@ -144,9 +144,15 @@ def update_site(env):
|
||||
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
|
||||
b.data = b.data.replace('std::FILE*', 'std::FILE *')
|
||||
b.data = b.data.replace('unsigned int', 'unsigned')
|
||||
b.data = b.data.replace('operator""_', 'operator"" _')
|
||||
#b.data = b.data.replace('operator""_', 'operator"" _')
|
||||
b.data = b.data.replace(', size_t', ', std::size_t')
|
||||
b.data = b.data.replace('aa long', 'a long')
|
||||
if version == '6.2.0':
|
||||
b.data = b.data.replace(
|
||||
'vformat(const S&, basic_format_args<' +
|
||||
'buffer_context<Char>>)',
|
||||
'vformat(const S&, basic_format_args<' +
|
||||
'buffer_context<type_identity_t<Char>>>)')
|
||||
# Fix a broken link in index.rst.
|
||||
index = os.path.join(target_doc_dir, 'index.rst')
|
||||
with rewrite(index) as b:
|
||||
|
||||
@ -95,7 +95,6 @@ add_fmt_test(grisu-test)
|
||||
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
|
||||
add_fmt_test(gtest-extra-test)
|
||||
add_fmt_test(format-test mock-allocator.h)
|
||||
add_fmt_test(format-dyn-args-test)
|
||||
if (MSVC)
|
||||
target_compile_options(format-test PRIVATE /bigobj)
|
||||
endif ()
|
||||
|
||||
@ -50,6 +50,8 @@ TEST(ColorsTest, ColorsPrint) {
|
||||
TEST(ColorsTest, Format) {
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
|
||||
L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
|
||||
@ -15,9 +15,8 @@
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "test-assert.h"
|
||||
|
||||
#include "gmock.h"
|
||||
#include "test-assert.h"
|
||||
|
||||
// Check if fmt/core.h compiles with windows.h included before it.
|
||||
#ifdef _WIN32
|
||||
@ -211,7 +210,8 @@ TEST(ArgTest, FormatArgs) {
|
||||
}
|
||||
|
||||
struct custom_context {
|
||||
typedef char char_type;
|
||||
using char_type = char;
|
||||
using parse_context_type = fmt::format_parse_context;
|
||||
|
||||
template <typename T> struct formatter_type {
|
||||
template <typename ParseContext>
|
||||
@ -402,6 +402,86 @@ TEST(ArgTest, VisitInvalidArg) {
|
||||
fmt::visit_format_arg(visitor, arg);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, Basic) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc1");
|
||||
store.push_back(1.5f);
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("42 and abc1 and 1.5", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, StringsAndRefs) {
|
||||
// Unfortunately the tests are compiled with old ABI so strings use COW.
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char str[] = "1234567890";
|
||||
store.push_back(str);
|
||||
store.push_back(std::cref(str));
|
||||
store.push_back(fmt::string_view{str});
|
||||
str[0] = 'X';
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("1234567890 and X234567890 and X234567890", result);
|
||||
}
|
||||
|
||||
struct custom_type {
|
||||
int i = 0;
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<custom_type> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return format_to(ctx.out(), "cust={}", p.i);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(FormatDynArgsTest, CustomFormat) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
custom_type c{};
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(std::cref(c));
|
||||
++c.i;
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
struct copy_throwable {
|
||||
copy_throwable() {}
|
||||
copy_throwable(const copy_throwable&) { throw "deal with it"; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<copy_throwable> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(FormatDynArgsTest, ThrowOnCopy) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(std::string("foo"));
|
||||
try {
|
||||
store.push_back(copy_throwable());
|
||||
} catch (...) {
|
||||
}
|
||||
EXPECT_EQ(fmt::vformat("{}", store), "foo");
|
||||
}
|
||||
|
||||
TEST(StringViewTest, ValueType) {
|
||||
static_assert(std::is_same<string_view::value_type, char>::value, "");
|
||||
}
|
||||
@ -520,23 +600,6 @@ inline fmt::basic_string_view<Char> to_string_view(const my_string<Char>& s)
|
||||
struct non_string {};
|
||||
} // namespace my_ns
|
||||
|
||||
namespace FakeQt {
|
||||
class QString {
|
||||
public:
|
||||
QString(const wchar_t* s) : s_(std::make_shared<std::wstring>(s)) {}
|
||||
const wchar_t* utf16() const FMT_NOEXCEPT { return s_->data(); }
|
||||
int size() const FMT_NOEXCEPT { return static_cast<int>(s_->size()); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::wstring> s_;
|
||||
};
|
||||
|
||||
inline fmt::basic_string_view<wchar_t> to_string_view(const QString& s)
|
||||
FMT_NOEXCEPT {
|
||||
return {s.utf16(), static_cast<std::size_t>(s.size())};
|
||||
}
|
||||
} // namespace FakeQt
|
||||
|
||||
template <typename T> class IsStringTest : public testing::Test {};
|
||||
|
||||
typedef ::testing::Types<char, wchar_t, char16_t, char32_t> StringCharTypes;
|
||||
@ -562,7 +625,6 @@ TYPED_TEST(IsStringTest, IsString) {
|
||||
fmt::internal::is_string<string_view>::value);
|
||||
EXPECT_TRUE(fmt::internal::is_string<my_ns::my_string<TypeParam>>::value);
|
||||
EXPECT_FALSE(fmt::internal::is_string<my_ns::non_string>::value);
|
||||
EXPECT_TRUE(fmt::internal::is_string<FakeQt::QString>::value);
|
||||
}
|
||||
|
||||
TEST(CoreTest, Format) {
|
||||
@ -585,33 +647,16 @@ TEST(CoreTest, FormatTo) {
|
||||
|
||||
TEST(CoreTest, ToStringViewForeignStrings) {
|
||||
using namespace my_ns;
|
||||
using namespace FakeQt;
|
||||
EXPECT_EQ(to_string_view(my_string<char>("42")), "42");
|
||||
EXPECT_EQ(to_string_view(my_string<wchar_t>(L"42")), L"42");
|
||||
EXPECT_EQ(to_string_view(QString(L"42")), L"42");
|
||||
fmt::internal::type type =
|
||||
fmt::internal::mapped_type_constant<my_string<char>,
|
||||
fmt::format_context>::value;
|
||||
EXPECT_EQ(type, fmt::internal::type::string_type);
|
||||
type = fmt::internal::mapped_type_constant<my_string<wchar_t>,
|
||||
fmt::wformat_context>::value;
|
||||
EXPECT_EQ(type, fmt::internal::type::string_type);
|
||||
type =
|
||||
fmt::internal::mapped_type_constant<QString, fmt::wformat_context>::value;
|
||||
EXPECT_EQ(type, fmt::internal::type::string_type);
|
||||
// Does not compile: only wide format contexts are compatible with QString!
|
||||
// type = fmt::internal::mapped_type_constant<QString,
|
||||
// fmt::format_context>::value;
|
||||
}
|
||||
|
||||
TEST(CoreTest, FormatForeignStrings) {
|
||||
using namespace my_ns;
|
||||
using namespace FakeQt;
|
||||
EXPECT_EQ(fmt::format(my_string<char>("{}"), 42), "42");
|
||||
EXPECT_EQ(fmt::format(my_string<wchar_t>(L"{}"), 42), L"42");
|
||||
EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
|
||||
EXPECT_EQ(fmt::format(QString(L"{}"), my_string<wchar_t>(L"42")), L"42");
|
||||
EXPECT_EQ(fmt::format(my_string<wchar_t>(L"{}"), QString(L"42")), L"42");
|
||||
}
|
||||
|
||||
struct implicitly_convertible_to_string {
|
||||
@ -646,15 +691,6 @@ TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
|
||||
fmt::format("{}", explicitly_convertible_to_std_string_view()));
|
||||
}
|
||||
# endif
|
||||
|
||||
struct explicitly_convertible_to_wstring_view {
|
||||
explicit operator fmt::wstring_view() const { return L"foo"; }
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToWStringView) {
|
||||
EXPECT_EQ(L"foo",
|
||||
fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
|
||||
}
|
||||
#endif
|
||||
|
||||
struct disabled_rvalue_conversion {
|
||||
|
||||
@ -4,84 +4,3 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "gtest-extra.h"
|
||||
|
||||
TEST(FormatDynArgsTest, Basic) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc1");
|
||||
store.push_back(1.5f);
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
|
||||
EXPECT_EQ("42 and abc1 and 1.5", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, StringsAndRefs) {
|
||||
// Unfortunately the tests are compiled with old ABI
|
||||
// So strings use COW.
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char str[]{"1234567890"};
|
||||
store.push_back(str);
|
||||
store.push_back(std::cref(str));
|
||||
store.push_back(fmt::string_view{str});
|
||||
str[0] = 'X';
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
|
||||
EXPECT_EQ("1234567890 and X234567890 and X234567890", result);
|
||||
}
|
||||
|
||||
struct custom_type {
|
||||
int i{0};
|
||||
};
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <> struct formatter<custom_type> {
|
||||
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to(
|
||||
ctx.out(), std::declval<typename FormatContext::char_type const*>())) {
|
||||
return format_to(ctx.out(), "cust={}", p.i);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(FormatDynArgsTest, CustomFormat) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
custom_type c{};
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(std::cref(c));
|
||||
++c.i;
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, NamedArgByRef) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
|
||||
// Note: fmt::arg() constructs an object which holds a reference
|
||||
// to its value. It's not an aggregate, so it doesn't extend the
|
||||
// reference lifetime. As a result, it's a very bad idea passing temporary
|
||||
// as a named argument value. Only GCC with optimization level >0
|
||||
// complains about this.
|
||||
//
|
||||
// A real life usecase is when you have both name and value alive
|
||||
// guarantee their lifetime and thus don't want them to be copied into
|
||||
// storages.
|
||||
int a1_val{42};
|
||||
auto a1 = fmt::arg("a1_", a1_val);
|
||||
store.push_back(std::cref(a1));
|
||||
|
||||
std::string result = fmt::vformat("{a1_}", // and {} and {}",
|
||||
store);
|
||||
|
||||
EXPECT_EQ("42", result);
|
||||
}
|
||||
|
||||
@ -10,12 +10,11 @@
|
||||
#include "test-assert.h"
|
||||
|
||||
// Include format.cc instead of format.h to test implementation.
|
||||
#include "../src/format.cc"
|
||||
#include "fmt/printf.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/format.cc"
|
||||
#include "fmt/printf.h"
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
@ -430,8 +429,10 @@ TEST(FormatTest, CountCodePoints) {
|
||||
#ifndef __cpp_char8_t
|
||||
using fmt::char8_t;
|
||||
#endif
|
||||
EXPECT_EQ(4, fmt::internal::count_code_points(
|
||||
fmt::basic_string_view<char8_t>(reinterpret_cast<const char8_t*>("ёжик"))));
|
||||
EXPECT_EQ(
|
||||
4, fmt::internal::count_code_points(
|
||||
fmt::basic_string_view<fmt::internal::char8_type>(
|
||||
reinterpret_cast<const fmt::internal::char8_type*>("ёжик"))));
|
||||
}
|
||||
|
||||
// Tests fmt::internal::count_digits for integer type Int.
|
||||
|
||||
@ -46,6 +46,7 @@ using fmt::format_error;
|
||||
using fmt::memory_buffer;
|
||||
using fmt::string_view;
|
||||
using fmt::wmemory_buffer;
|
||||
using fmt::wstring_view;
|
||||
using fmt::internal::basic_writer;
|
||||
using fmt::internal::max_value;
|
||||
|
||||
@ -639,15 +640,14 @@ TEST(FormatterTest, ArgErrors) {
|
||||
EXPECT_THROW_MSG(format("{"), format_error, "invalid format string");
|
||||
EXPECT_THROW_MSG(format("{?}"), format_error, "invalid format string");
|
||||
EXPECT_THROW_MSG(format("{0"), format_error, "invalid format string");
|
||||
EXPECT_THROW_MSG(format("{0}"), format_error, "argument index out of range");
|
||||
EXPECT_THROW_MSG(format("{0}"), format_error, "argument not found");
|
||||
EXPECT_THROW_MSG(format("{00}", 42), format_error, "invalid format string");
|
||||
|
||||
char format_str[BUFFER_SIZE];
|
||||
safe_sprintf(format_str, "{%u", INT_MAX);
|
||||
EXPECT_THROW_MSG(format(format_str), format_error, "invalid format string");
|
||||
safe_sprintf(format_str, "{%u}", INT_MAX);
|
||||
EXPECT_THROW_MSG(format(format_str), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(format(format_str), format_error, "argument not found");
|
||||
|
||||
safe_sprintf(format_str, "{%u", INT_MAX + 1u);
|
||||
EXPECT_THROW_MSG(format(format_str), format_error, "number is too big");
|
||||
@ -672,13 +672,13 @@ template <> struct TestFormat<0> {
|
||||
TEST(FormatterTest, ManyArgs) {
|
||||
EXPECT_EQ("19", TestFormat<20>::format("{19}"));
|
||||
EXPECT_THROW_MSG(TestFormat<20>::format("{20}"), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
EXPECT_THROW_MSG(TestFormat<21>::format("{21}"), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
enum { max_packed_args = fmt::internal::max_packed_args };
|
||||
std::string format_str = fmt::format("{{{}}}", max_packed_args + 1);
|
||||
EXPECT_THROW_MSG(TestFormat<max_packed_args>::format(format_str),
|
||||
format_error, "argument index out of range");
|
||||
format_error, "argument not found");
|
||||
}
|
||||
|
||||
TEST(FormatterTest, NamedArg) {
|
||||
@ -707,7 +707,7 @@ TEST(FormatterTest, AutoArgIndex) {
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG(format("{:.{0}}", 1.2345, 2), format_error,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
EXPECT_THROW_MSG(format("{}"), format_error, "argument index out of range");
|
||||
EXPECT_THROW_MSG(format("{}"), format_error, "argument not found");
|
||||
}
|
||||
|
||||
TEST(FormatterTest, EmptySpecs) { EXPECT_EQ("42", format("{0:}", 42)); }
|
||||
@ -1010,8 +1010,7 @@ TEST(FormatterTest, RuntimeWidth) {
|
||||
EXPECT_THROW_MSG(format("{0:{}", 0), format_error,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG(format("{0:{?}}", 0), format_error, "invalid format string");
|
||||
EXPECT_THROW_MSG(format("{0:{1}}", 0), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(format("{0:{1}}", 0), format_error, "argument not found");
|
||||
|
||||
EXPECT_THROW_MSG(format("{0:{0:}}", 0), format_error,
|
||||
"invalid format string");
|
||||
@ -1124,6 +1123,8 @@ TEST(FormatterTest, Precision) {
|
||||
"000000000000000000000000000000000000000000000000000P+127",
|
||||
format("{:.838A}", -2.14001164E+38));
|
||||
EXPECT_EQ("123.", format("{:#.0f}", 123.0));
|
||||
EXPECT_EQ("1.23", format("{:.02f}", 1.234));
|
||||
EXPECT_EQ("0.001", format("{:.1g}", 0.001));
|
||||
|
||||
EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast<void*>(0xcafe)),
|
||||
format_error,
|
||||
@ -1157,8 +1158,7 @@ TEST(FormatterTest, RuntimePrecision) {
|
||||
"invalid format string");
|
||||
EXPECT_THROW_MSG(format("{0:.{1}", 0, 0), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_THROW_MSG(format("{0:.{1}}", 0), format_error,
|
||||
"argument index out of range");
|
||||
EXPECT_THROW_MSG(format("{0:.{1}}", 0), format_error, "argument not found");
|
||||
|
||||
EXPECT_THROW_MSG(format("{0:.{0:}}", 0), format_error,
|
||||
"invalid format string");
|
||||
@ -1253,7 +1253,7 @@ TEST(FormatterTest, FormatShort) {
|
||||
TEST(FormatterTest, FormatInt) {
|
||||
EXPECT_THROW_MSG(format("{0:v", 42), format_error,
|
||||
"missing '}' in format string");
|
||||
check_unknown_types(42, "bBdoxXn", "integer");
|
||||
check_unknown_types(42, "bBdoxXnL", "integer");
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatBin) {
|
||||
@ -1394,6 +1394,7 @@ TEST(FormatterTest, FormatOct) {
|
||||
|
||||
TEST(FormatterTest, FormatIntLocale) {
|
||||
EXPECT_EQ("1234", format("{:n}", 1234));
|
||||
EXPECT_EQ("1234", format("{:L}", 1234));
|
||||
}
|
||||
|
||||
struct ConvertibleToLongLong {
|
||||
@ -1409,7 +1410,7 @@ TEST(FormatterTest, FormatFloat) {
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatDouble) {
|
||||
check_unknown_types(1.2, "eEfFgGaAn%", "double");
|
||||
check_unknown_types(1.2, "eEfFgGaAnL%", "double");
|
||||
EXPECT_EQ("0.0", format("{:}", 0.0));
|
||||
EXPECT_EQ("0.000000", format("{:f}", 0.0));
|
||||
EXPECT_EQ("0", format("{:g}", 0.0));
|
||||
@ -1418,6 +1419,7 @@ TEST(FormatterTest, FormatDouble) {
|
||||
EXPECT_EQ("392.65", format("{:G}", 392.65));
|
||||
EXPECT_EQ("392.650000", format("{:f}", 392.65));
|
||||
EXPECT_EQ("392.650000", format("{:F}", 392.65));
|
||||
EXPECT_EQ("42", format("{:L}", 42.0));
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%e", 392.65);
|
||||
EXPECT_EQ(buffer, format("{0:e}", 392.65));
|
||||
@ -1488,7 +1490,7 @@ TEST(FormatterTest, FormatLongDouble) {
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatChar) {
|
||||
const char types[] = "cbBdoxXn";
|
||||
const char types[] = "cbBdoxXnL";
|
||||
check_unknown_types('a', types, "char");
|
||||
EXPECT_EQ("a", format("{0}", 'a'));
|
||||
EXPECT_EQ("z", format("{0:c}", 'z'));
|
||||
@ -1604,6 +1606,40 @@ TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// std::is_constructible is broken in MSVC until version 2015.
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER >= 1900
|
||||
struct explicitly_convertible_to_wstring_view {
|
||||
explicit operator fmt::wstring_view() const { return L"foo"; }
|
||||
};
|
||||
|
||||
TEST(FormatTest, FormatExplicitlyConvertibleToWStringView) {
|
||||
EXPECT_EQ(L"foo",
|
||||
fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace fake_qt {
|
||||
class QString {
|
||||
public:
|
||||
QString(const wchar_t* s) : s_(std::make_shared<std::wstring>(s)) {}
|
||||
const wchar_t* utf16() const FMT_NOEXCEPT { return s_->data(); }
|
||||
int size() const FMT_NOEXCEPT { return static_cast<int>(s_->size()); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::wstring> s_;
|
||||
};
|
||||
|
||||
fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT {
|
||||
return {s.utf16(), static_cast<std::size_t>(s.size())};
|
||||
}
|
||||
} // namespace fake_qt
|
||||
|
||||
TEST(FormatTest, FormatForeignStrings) {
|
||||
using fake_qt::QString;
|
||||
EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
|
||||
EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42");
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<Date> {
|
||||
template <typename ParseContext>
|
||||
@ -1754,6 +1790,8 @@ TEST(FormatTest, Print) {
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
|
||||
"Don't panic!");
|
||||
#endif
|
||||
// Check that the wide print overload compiles.
|
||||
if (fmt::internal::const_check(false)) fmt::print(L"test");
|
||||
}
|
||||
|
||||
TEST(FormatTest, Variadic) {
|
||||
@ -1852,10 +1890,23 @@ TEST(FormatTest, UnpackedArgs) {
|
||||
struct string_like {};
|
||||
fmt::string_view to_string_view(string_like) { return "foo"; }
|
||||
|
||||
constexpr char with_null[3] = {'{', '}', '\0'};
|
||||
constexpr char no_null[2] = {'{', '}'};
|
||||
|
||||
TEST(FormatTest, CompileTimeString) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
|
||||
(void)with_null;
|
||||
(void)no_null;
|
||||
#if __cplusplus >= 201703L
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42));
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42));
|
||||
#endif
|
||||
#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42));
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(FormatTest, CustomFormatCompileTimeString) {
|
||||
@ -2240,8 +2291,9 @@ struct test_parse_context {
|
||||
};
|
||||
|
||||
struct test_context {
|
||||
typedef char char_type;
|
||||
typedef fmt::basic_format_arg<test_context> format_arg;
|
||||
using char_type = char;
|
||||
using format_arg = fmt::basic_format_arg<test_context>;
|
||||
using parse_context_type = fmt::format_parse_context;
|
||||
|
||||
template <typename T> struct formatter_type {
|
||||
typedef fmt::formatter<T, char_type> type;
|
||||
@ -2401,8 +2453,10 @@ FMT_CONSTEXPR bool equal(const char* s1, const char* s2) {
|
||||
template <typename... Args>
|
||||
FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
|
||||
const char* actual_error = nullptr;
|
||||
fmt::internal::do_check_format_string<char, test_error_handler, Args...>(
|
||||
string_view(fmt, len(fmt)), test_error_handler(actual_error));
|
||||
string_view s(fmt, len(fmt));
|
||||
fmt::internal::format_string_checker<char, test_error_handler, Args...>
|
||||
checker(s, test_error_handler(actual_error));
|
||||
fmt::internal::parse_format_string<true>(s, checker);
|
||||
return equal(actual_error, expected_error);
|
||||
}
|
||||
|
||||
@ -2415,12 +2469,12 @@ TEST(FormatTest, FormatStringErrors) {
|
||||
EXPECT_ERROR_NOARGS("foo", nullptr);
|
||||
EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string");
|
||||
EXPECT_ERROR("{0:s", "unknown format specifier", Date);
|
||||
# if FMT_MSC_VER >= 1916
|
||||
# if !FMT_MSC_VER || FMT_MSC_VER >= 1916
|
||||
// This causes an internal compiler error in MSVC2017.
|
||||
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
|
||||
EXPECT_ERROR("{:10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR("{:.10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR_NOARGS("{:x}", "argument index out of range");
|
||||
EXPECT_ERROR_NOARGS("{:x}", "argument not found");
|
||||
# if FMT_NUMERIC_ALIGN
|
||||
EXPECT_ERROR("{0:=5", "unknown format specifier", int);
|
||||
EXPECT_ERROR("{:=}", "format specifier requires numeric argument",
|
||||
@ -2439,6 +2493,8 @@ TEST(FormatTest, FormatStringErrors) {
|
||||
EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{:{}}", "argument not found", int);
|
||||
EXPECT_ERROR("{:.{}}", "argument not found", double);
|
||||
EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", int);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", bool);
|
||||
@ -2461,8 +2517,8 @@ TEST(FormatTest, FormatStringErrors) {
|
||||
EXPECT_ERROR("{:.{0x}}", "invalid format string", int);
|
||||
EXPECT_ERROR("{:.{-}}", "invalid format string", int);
|
||||
EXPECT_ERROR("{:.x}", "missing precision specifier", int);
|
||||
EXPECT_ERROR_NOARGS("{}", "argument index out of range");
|
||||
EXPECT_ERROR("{1}", "argument index out of range", int);
|
||||
EXPECT_ERROR_NOARGS("{}", "argument not found");
|
||||
EXPECT_ERROR("{1}", "argument not found", int);
|
||||
EXPECT_ERROR("{1}{}",
|
||||
"cannot switch from manual to automatic argument indexing", int,
|
||||
int);
|
||||
@ -2504,25 +2560,6 @@ TEST(FormatTest, FmtStringInTemplate) {
|
||||
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
// C++20 feature test, since r346892 Clang considers char8_t a fundamental
|
||||
// type in this mode. If this is the case __cpp_char8_t will be defined.
|
||||
#ifndef __cpp_char8_t
|
||||
// Locally provide type char8_t defined in format.h
|
||||
using fmt::char8_t;
|
||||
#endif
|
||||
|
||||
// Convert 'char8_t' character sequences to 'char' sequences
|
||||
// Otherwise GTest will insist on inserting 'char8_t' NTBS into a 'char' stream,
|
||||
// but basic_ostream<char>::operator<< overloads taking 'char8_t' arguments
|
||||
// are defined as deleted by P1423.
|
||||
// Handling individual 'char8_t's is done inline.
|
||||
std::string from_u8str(const std::basic_string<char8_t>& str) {
|
||||
return std::string(str.begin(), str.end());
|
||||
}
|
||||
std::string from_u8str(const fmt::basic_string_view<char8_t>& str) {
|
||||
return std::string(str.begin(), str.end());
|
||||
}
|
||||
|
||||
TEST(FormatTest, EmphasisNonHeaderOnly) {
|
||||
// Ensure this compiles even if FMT_HEADER_ONLY is not defined.
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"),
|
||||
@ -2561,10 +2598,18 @@ TEST(FormatTest, FormatCustomChar) {
|
||||
EXPECT_EQ(result[0], mychar('x'));
|
||||
}
|
||||
|
||||
// Convert a char8_t string to std::string. Otherwise GTest will insist on
|
||||
// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423.
|
||||
template <typename S> std::string from_u8str(const S& str) {
|
||||
return std::string(str.begin(), str.end());
|
||||
}
|
||||
|
||||
TEST(FormatTest, FormatUTF8Precision) {
|
||||
using str_type = std::basic_string<char8_t>;
|
||||
str_type format(reinterpret_cast<const char8_t*>(u8"{:.4}"));
|
||||
str_type str(reinterpret_cast<const char8_t*>(u8"caf\u00e9s")); // cafés
|
||||
using str_type = std::basic_string<fmt::internal::char8_type>;
|
||||
str_type format(
|
||||
reinterpret_cast<const fmt::internal::char8_type*>(u8"{:.4}"));
|
||||
str_type str(reinterpret_cast<const fmt::internal::char8_type*>(
|
||||
u8"caf\u00e9s")); // cafés
|
||||
auto result = fmt::format(format, str);
|
||||
EXPECT_EQ(fmt::internal::count_code_points(result), 4);
|
||||
EXPECT_EQ(result.size(), 5);
|
||||
|
||||
@ -7,14 +7,19 @@ in fmt. It is a part of the continous fuzzing at
|
||||
The source code is modified to make the fuzzing possible without locking up on
|
||||
resource exhaustion:
|
||||
```cpp
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#ifdef FMT_FUZZ
|
||||
if(spec.precision>100000) {
|
||||
throw std::runtime_error("fuzz mode - avoiding large precision");
|
||||
}
|
||||
#endif
|
||||
```
|
||||
This macro is the defacto standard for making fuzzing practically possible, see
|
||||
[the libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode).
|
||||
```
|
||||
This macro `FMT_FUZZ` is enabled on OSS-Fuzz builds and makes fuzzing
|
||||
practically possible. It is used in fmt code to prevent resource exhaustion in
|
||||
fuzzing mode.
|
||||
The macro `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` is the
|
||||
defacto standard for making fuzzing practically possible to disable certain
|
||||
fuzzing-unfriendly features (for example, randomness), see [the libFuzzer
|
||||
documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode).
|
||||
|
||||
## Running the fuzzers locally
|
||||
|
||||
|
||||
@ -29,11 +29,11 @@ void invoke_fmt(const uint8_t* Data, std::size_t Size, unsigned int argsize) {
|
||||
// allocating buffers separately is slower, but increases chances
|
||||
// of detecting memory errors
|
||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||
std::vector<char> argnamebuffer(argsize);
|
||||
std::vector<char> argnamebuffer(argsize + 1);
|
||||
std::memcpy(argnamebuffer.data(), Data, argsize);
|
||||
auto argname = fmt::string_view(argnamebuffer.data(), argsize);
|
||||
auto argname = argnamebuffer.data();
|
||||
#else
|
||||
auto argname = fmt::string_view(fmt_fuzzer::as_chars(Data), argsize);
|
||||
auto argname = fmt_fuzzer::as_chars(Data);
|
||||
#endif
|
||||
Data += argsize;
|
||||
Size -= argsize;
|
||||
|
||||
@ -489,9 +489,9 @@ TEST(FileTest, Fdopen) {
|
||||
|
||||
# ifdef FMT_LOCALE
|
||||
TEST(LocaleTest, Strtod) {
|
||||
fmt::Locale locale;
|
||||
fmt::locale loc;
|
||||
const char *start = "4.2", *ptr = start;
|
||||
EXPECT_EQ(4.2, locale.strtod(ptr));
|
||||
EXPECT_EQ(4.2, loc.strtod(ptr));
|
||||
EXPECT_EQ(start + 3, ptr);
|
||||
}
|
||||
# endif
|
||||
|
||||
@ -11,13 +11,15 @@
|
||||
#endif
|
||||
|
||||
#include "posix-mock.h"
|
||||
#include "../src/os.cc"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <climits>
|
||||
#include <memory>
|
||||
|
||||
#include "../src/os.cc"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
# undef max
|
||||
@ -51,7 +53,7 @@ std::size_t read_nbyte;
|
||||
std::size_t write_nbyte;
|
||||
bool sysconf_error;
|
||||
|
||||
enum FStatSimulation { NONE, MAX_SIZE, ERROR } fstat_sim;
|
||||
enum { NONE, MAX_SIZE, ERROR } fstat_sim;
|
||||
} // namespace
|
||||
|
||||
#define EMULATE_EINTR(func, error_result) \
|
||||
@ -215,10 +217,10 @@ TEST(UtilTest, GetPageSize) {
|
||||
}
|
||||
|
||||
TEST(FileTest, OpenRetry) {
|
||||
write_file("test", "there must be something here");
|
||||
write_file("temp", "there must be something here");
|
||||
std::unique_ptr<file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new file("test", file::RDONLY)), open,
|
||||
"cannot open file test");
|
||||
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
|
||||
"cannot open file temp");
|
||||
# ifndef _WIN32
|
||||
char c = 0;
|
||||
f->read(&c, 1);
|
||||
@ -230,14 +232,15 @@ TEST(FileTest, CloseNoRetryInDtor) {
|
||||
file::pipe(read_end, write_end);
|
||||
std::unique_ptr<file> f(new file(std::move(read_end)));
|
||||
int saved_close_count = 0;
|
||||
EXPECT_WRITE(stderr,
|
||||
{
|
||||
close_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_close_count = close_count;
|
||||
close_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_WRITE(
|
||||
stderr,
|
||||
{
|
||||
close_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_close_count = close_count;
|
||||
close_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_EQ(2, saved_close_count);
|
||||
}
|
||||
|
||||
@ -252,8 +255,8 @@ TEST(FileTest, CloseNoRetry) {
|
||||
|
||||
TEST(FileTest, Size) {
|
||||
std::string content = "top secret, destroy before reading";
|
||||
write_file("test", content);
|
||||
file f("test", file::RDONLY);
|
||||
write_file("temp", content);
|
||||
file f("temp", file::RDONLY);
|
||||
EXPECT_GE(f.size(), 0);
|
||||
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
|
||||
# ifdef _WIN32
|
||||
@ -270,8 +273,8 @@ TEST(FileTest, Size) {
|
||||
}
|
||||
|
||||
TEST(FileTest, MaxSize) {
|
||||
write_file("test", "");
|
||||
file f("test", file::RDONLY);
|
||||
write_file("temp", "");
|
||||
file f("temp", file::RDONLY);
|
||||
fstat_sim = MAX_SIZE;
|
||||
EXPECT_GE(f.size(), 0);
|
||||
EXPECT_EQ(max_file_size(), f.size());
|
||||
@ -385,10 +388,10 @@ TEST(FileTest, FdopenNoRetry) {
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, OpenRetry) {
|
||||
write_file("test", "there must be something here");
|
||||
write_file("temp", "there must be something here");
|
||||
std::unique_ptr<buffered_file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new buffered_file("test", "r")), fopen,
|
||||
"cannot open file test");
|
||||
EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
|
||||
"cannot open file temp");
|
||||
# ifndef _WIN32
|
||||
char c = 0;
|
||||
if (fread(&c, 1, 1, f->get()) < 1)
|
||||
@ -401,14 +404,15 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
||||
file::pipe(read_end, write_end);
|
||||
std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
|
||||
int saved_fclose_count = 0;
|
||||
EXPECT_WRITE(stderr,
|
||||
{
|
||||
fclose_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_fclose_count = fclose_count;
|
||||
fclose_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_WRITE(
|
||||
stderr,
|
||||
{
|
||||
fclose_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_fclose_count = fclose_count;
|
||||
fclose_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_EQ(2, saved_fclose_count);
|
||||
}
|
||||
|
||||
@ -433,33 +437,33 @@ TEST(BufferedFileTest, FilenoNoRetry) {
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
struct TestMock {
|
||||
static TestMock* instance;
|
||||
} * TestMock::instance;
|
||||
struct test_mock {
|
||||
static test_mock* instance;
|
||||
} * test_mock::instance;
|
||||
|
||||
TEST(ScopedMock, Scope) {
|
||||
{
|
||||
ScopedMock<TestMock> mock;
|
||||
EXPECT_EQ(&mock, TestMock::instance);
|
||||
TestMock& copy = mock;
|
||||
ScopedMock<test_mock> mock;
|
||||
EXPECT_EQ(&mock, test_mock::instance);
|
||||
test_mock& copy = mock;
|
||||
static_cast<void>(copy);
|
||||
}
|
||||
EXPECT_EQ(nullptr, TestMock::instance);
|
||||
EXPECT_EQ(nullptr, test_mock::instance);
|
||||
}
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
|
||||
typedef fmt::Locale::type LocaleType;
|
||||
typedef fmt::locale::type locale_type;
|
||||
|
||||
struct LocaleMock {
|
||||
static LocaleMock* instance;
|
||||
MOCK_METHOD3(newlocale, LocaleType(int category_mask, const char* locale,
|
||||
LocaleType base));
|
||||
MOCK_METHOD1(freelocale, void(LocaleType locale));
|
||||
struct locale_mock {
|
||||
static locale_mock* instance;
|
||||
MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale,
|
||||
locale_type base));
|
||||
MOCK_METHOD1(freelocale, void(locale_type locale));
|
||||
|
||||
MOCK_METHOD3(strtod_l,
|
||||
double(const char* nptr, char** endptr, LocaleType locale));
|
||||
} * LocaleMock::instance;
|
||||
double(const char* nptr, char** endptr, locale_type locale));
|
||||
} * locale_mock::instance;
|
||||
|
||||
# ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
@ -470,15 +474,15 @@ struct LocaleMock {
|
||||
# endif
|
||||
|
||||
_locale_t _create_locale(int category, const char* locale) {
|
||||
return LocaleMock::instance->newlocale(category, locale, 0);
|
||||
return locale_mock::instance->newlocale(category, locale, 0);
|
||||
}
|
||||
|
||||
void _free_locale(_locale_t locale) {
|
||||
LocaleMock::instance->freelocale(locale);
|
||||
locale_mock::instance->freelocale(locale);
|
||||
}
|
||||
|
||||
double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
return LocaleMock::instance->strtod_l(nptr, endptr, locale);
|
||||
return locale_mock::instance->strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# ifdef __clang__
|
||||
# pragma clang diagnostic pop
|
||||
@ -492,11 +496,6 @@ double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
# define FMT_LOCALE_THROW
|
||||
# endif
|
||||
|
||||
LocaleType newlocale(int category_mask, const char* locale,
|
||||
LocaleType base) FMT_LOCALE_THROW {
|
||||
return LocaleMock::instance->newlocale(category_mask, locale, base);
|
||||
}
|
||||
|
||||
# if defined(__APPLE__) || \
|
||||
(defined(__FreeBSD__) && __FreeBSD_version < 1200002)
|
||||
typedef int FreeLocaleResult;
|
||||
@ -504,49 +503,55 @@ typedef int FreeLocaleResult;
|
||||
typedef void FreeLocaleResult;
|
||||
# endif
|
||||
|
||||
FreeLocaleResult freelocale(LocaleType locale) FMT_LOCALE_THROW {
|
||||
LocaleMock::instance->freelocale(locale);
|
||||
FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW {
|
||||
locale_mock::instance->freelocale(locale);
|
||||
return FreeLocaleResult();
|
||||
}
|
||||
|
||||
double strtod_l(const char* nptr, char** endptr,
|
||||
LocaleType locale) FMT_LOCALE_THROW {
|
||||
return LocaleMock::instance->strtod_l(nptr, endptr, locale);
|
||||
locale_type locale) FMT_LOCALE_THROW {
|
||||
return locale_mock::instance->strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
|
||||
# undef FMT_LOCALE_THROW
|
||||
|
||||
TEST(LocaleTest, LocaleMock) {
|
||||
ScopedMock<LocaleMock> mock;
|
||||
LocaleType locale = reinterpret_cast<LocaleType>(11);
|
||||
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
||||
newlocale(222, "foo", locale);
|
||||
# ifndef _WIN32
|
||||
locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
|
||||
return locale_mock::instance->newlocale(category_mask, locale, base);
|
||||
}
|
||||
|
||||
TEST(LocaleTest, LocaleMock) {
|
||||
ScopedMock<locale_mock> mock;
|
||||
locale_type locale = reinterpret_cast<locale_type>(11);
|
||||
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
||||
FMT_SYSTEM(newlocale(222, "foo", locale));
|
||||
}
|
||||
# endif
|
||||
|
||||
TEST(LocaleTest, Locale) {
|
||||
# ifndef LC_NUMERIC_MASK
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
# endif
|
||||
ScopedMock<LocaleMock> mock;
|
||||
LocaleType impl = reinterpret_cast<LocaleType>(42);
|
||||
ScopedMock<locale_mock> mock;
|
||||
locale_type impl = reinterpret_cast<locale_type>(42);
|
||||
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
|
||||
.WillOnce(Return(impl));
|
||||
EXPECT_CALL(mock, freelocale(impl));
|
||||
fmt::Locale locale;
|
||||
EXPECT_EQ(impl, locale.get());
|
||||
fmt::locale loc;
|
||||
EXPECT_EQ(impl, loc.get());
|
||||
}
|
||||
|
||||
TEST(LocaleTest, Strtod) {
|
||||
ScopedMock<LocaleMock> mock;
|
||||
ScopedMock<locale_mock> mock;
|
||||
EXPECT_CALL(mock, newlocale(_, _, _))
|
||||
.WillOnce(Return(reinterpret_cast<LocaleType>(42)));
|
||||
.WillOnce(Return(reinterpret_cast<locale_type>(42)));
|
||||
EXPECT_CALL(mock, freelocale(_));
|
||||
fmt::Locale locale;
|
||||
fmt::locale loc;
|
||||
const char* str = "4.2";
|
||||
char end = 'x';
|
||||
EXPECT_CALL(mock, strtod_l(str, _, locale.get()))
|
||||
EXPECT_CALL(mock, strtod_l(str, _, loc.get()))
|
||||
.WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777)));
|
||||
EXPECT_EQ(777, locale.strtod(str));
|
||||
EXPECT_EQ(777, loc.strtod(str));
|
||||
EXPECT_EQ(&end, str);
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,11 @@
|
||||
#define FMT_POSIX_TEST_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#ifdef __APPLE__
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
@ -62,6 +66,10 @@ int pipe(int* pfds, unsigned psize, int textmode);
|
||||
FILE* fopen(const char* filename, const char* mode);
|
||||
int fclose(FILE* stream);
|
||||
int(fileno)(FILE* stream);
|
||||
|
||||
#if defined(FMT_LOCALE) && !defined(_WIN32)
|
||||
locale_t newlocale(int category_mask, const char* locale, locale_t base);
|
||||
#endif
|
||||
} // namespace test
|
||||
|
||||
#define FMT_SYSTEM(call) test::call
|
||||
|
||||
@ -113,14 +113,14 @@ TEST(PrintfTest, SwitchArgIndexing) {
|
||||
|
||||
TEST(PrintfTest, InvalidArgIndex) {
|
||||
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", INT_MAX), 42), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
|
||||
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM), 42), format_error,
|
||||
"number is too big");
|
||||
}
|
||||
@ -223,7 +223,7 @@ TEST(PrintfTest, DynamicWidth) {
|
||||
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
|
||||
"width is not integer");
|
||||
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
EXPECT_THROW_MSG(test_sprintf("%*d", BIG_NUM, 42), format_error,
|
||||
"number is too big");
|
||||
}
|
||||
@ -259,6 +259,11 @@ TEST(PrintfTest, FloatPrecision) {
|
||||
EXPECT_PRINTF(buffer, "%.3a", 1234.5678);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, StringPrecision) {
|
||||
char test[] = {'H', 'e', 'l', 'l', 'o'};
|
||||
EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell");
|
||||
}
|
||||
|
||||
TEST(PrintfTest, IgnorePrecisionForNonNumericArg) {
|
||||
EXPECT_PRINTF("abc", "%.5s", "abc");
|
||||
}
|
||||
@ -269,7 +274,7 @@ TEST(PrintfTest, DynamicPrecision) {
|
||||
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
|
||||
"precision is not integer");
|
||||
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error,
|
||||
"argument index out of range");
|
||||
"argument not found");
|
||||
EXPECT_THROW_MSG(test_sprintf("%.*d", BIG_NUM, 42), format_error,
|
||||
"number is too big");
|
||||
if (sizeof(long long) != sizeof(int)) {
|
||||
@ -447,6 +452,12 @@ TEST(PrintfTest, String) {
|
||||
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, UCharString) {
|
||||
unsigned char str[] = "test";
|
||||
unsigned char* pstr = str;
|
||||
EXPECT_EQ("test", fmt::sprintf("%s", pstr));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Pointer) {
|
||||
int n;
|
||||
void* p = &n;
|
||||
@ -502,9 +513,7 @@ TEST(PrintfTest, PrintfError) {
|
||||
TEST(PrintfTest, WideString) { EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); }
|
||||
|
||||
TEST(PrintfTest, PrintfCustom) {
|
||||
// The test is disabled for now because it requires decoupling
|
||||
// fallback_formatter::format from format_context.
|
||||
//EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
|
||||
EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, OStream) {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#include "fmt/ranges.h"
|
||||
|
||||
#include "gtest.h"
|
||||
|
||||
// Check if 'if constexpr' is supported.
|
||||
@ -44,9 +45,10 @@ TEST(RangesTest, FormatPair) {
|
||||
}
|
||||
|
||||
TEST(RangesTest, FormatTuple) {
|
||||
std::tuple<int64_t, float, std::string, char> tu1{42, 1.5f, "this is tuple",
|
||||
'i'};
|
||||
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1));
|
||||
std::tuple<int64_t, float, std::string, char> t{42, 1.5f, "this is tuple",
|
||||
'i'};
|
||||
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t));
|
||||
EXPECT_EQ("()", fmt::format("{}", std::tuple<>()));
|
||||
}
|
||||
|
||||
TEST(RangesTest, JoinTuple) {
|
||||
@ -68,6 +70,12 @@ TEST(RangesTest, JoinTuple) {
|
||||
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
|
||||
}
|
||||
|
||||
TEST(RangesTest, JoinInitializerList) {
|
||||
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join({1, 2, 3}, ", ")));
|
||||
EXPECT_EQ("fmt rocks !",
|
||||
fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")));
|
||||
}
|
||||
|
||||
struct my_struct {
|
||||
int32_t i;
|
||||
std::string str; // can throw
|
||||
|
||||
Loading…
Reference in New Issue
Block a user