Merge remote-tracking branch 'zverovich/master'

Conflicts:
	include/fmt/core.h
	test/format-dyn-args-test.cc
This commit is contained in:
vsol 2020-05-03 12:23:02 +03:00
commit baae799489
34 changed files with 1645 additions and 1168 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.vscode/ .vscode/
.vs/
*.iml *.iml
.idea/ .idea/

View File

@ -54,7 +54,7 @@ option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT}) option(FMT_TEST "Generate the test target." ${MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test 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) project(FMT CXX)
# Get version from core.h # Get version from core.h
@ -81,6 +81,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
include(cxx14) include(cxx14)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
include(JoinPaths)
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
if (${index} GREATER -1) if (${index} GREATER -1)
@ -173,7 +174,11 @@ endfunction()
# Define the fmt library, its includes and the needed defines. # 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 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) 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_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
add_library(fmt::fmt ALIAS fmt) add_library(fmt::fmt ALIAS fmt)
@ -201,8 +206,9 @@ set_target_properties(fmt PROPERTIES
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
# Set FMT_LIB_NAME for pkg-config fmt.pc. # Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
get_target_property(FMT_LIB_NAME fmt OUTPUT_NAME) # property because it's not set by default.
set(FMT_LIB_NAME fmt)
if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX}) set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
endif () endif ()
@ -234,8 +240,8 @@ if (FMT_INSTALL)
include(GNUInstallDirs) include(GNUInstallDirs)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
"Installation directory for cmake files, relative to " "Installation directory for cmake files, a relative path "
"${CMAKE_INSTALL_PREFIX}.") "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
@ -247,22 +253,26 @@ if (FMT_INSTALL)
endif () endif ()
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
"Installation directory for libraries, relative to " "Installation directory for libraries, a relative path "
"${CMAKE_INSTALL_PREFIX}.") "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING
"Installation directory for include files, relative to " "Installation directory for include files, a relative path "
"${CMAKE_INSTALL_PREFIX}.") "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
"Installation directory for pkgconfig (.pc) files, relative to " "Installation directory for pkgconfig (.pc) files, a relative path "
"${CMAKE_INSTALL_PREFIX}.") "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
# Generate the version, config and target files into the build directory. # Generate the version, config and target files into the build directory.
write_basic_package_version_file( write_basic_package_version_file(
${version_config} ${version_config}
VERSION ${FMT_VERSION} VERSION ${FMT_VERSION}
COMPATIBILITY AnyNewerVersion) 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( configure_file(
"${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in" "${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
"${pkgconfig}" "${pkgconfig}"
@ -307,6 +317,7 @@ endif ()
# Control fuzzing independent of the unit tests. # Control fuzzing independent of the unit tests.
if (FMT_FUZZ) if (FMT_FUZZ)
add_subdirectory(test/fuzzing) add_subdirectory(test/fuzzing)
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
endif () endif ()
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)

View File

@ -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 6.1.2 - 2019-12-11
------------------ ------------------

View File

@ -264,8 +264,8 @@ or the bloat test::
Projects using this library Projects using this library
--------------------------- ---------------------------
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform real-time * `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform
strategy game real-time strategy game
* `AMPL/MP <https://github.com/ampl/mp>`_: * `AMPL/MP <https://github.com/ampl/mp>`_:
An open-source library for mathematical programming An open-source library for mathematical programming
@ -282,6 +282,16 @@ Projects using this library
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater * `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle 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>`_: * `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
Player vs Player Gaming Network with tweaks 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 * `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 <https://mongodb.com/>`_: Distributed document database
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to * `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
generate randomized datasets generate randomized datasets
* `OpenSpace <https://openspaceproject.com/>`_: An open-source astrovisualization * `OpenSpace <https://openspaceproject.com/>`_: An open-source
framework astrovisualization framework
* `PenUltima Online (POL) <https://www.polserver.com/>`_: * `PenUltima Online (POL) <https://www.polserver.com/>`_:
An MMO server, compatible with most Ultima Online clients 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, * `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
associative database associative database
@ -320,13 +325,14 @@ Projects using this library
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster * `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
proxy 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 * `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client
library library
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_: * `Salesforce Analytics Cloud
Small crossplatform 2D graphic engine <https://www.salesforce.com/analytics-cloud/overview/>`_:
* `Salesforce Analytics Cloud <https://www.salesforce.com/analytics-cloud/overview/>`_:
Business intelligence software Business intelligence software
* `Scylla <https://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store * `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 * `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
MMORPG framework MMORPG framework
* `Windows Terminal <https://github.com/microsoft/terminal>`_: The new Windows
Terminal
`More... <https://github.com/search?q=fmtlib&type=Code>`_ `More... <https://github.com/search?q=fmtlib&type=Code>`_
If you are aware of other projects using this library, please let me know If you are aware of other projects using this library, please let me know

View File

@ -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 * :ref:`fmt/core.h <core-api>`: the core API providing argument handling
facilities and a lightweight subset of formatting functions facilities and a lightweight subset of formatting functions
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time * :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 * :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
and tuples and tuples
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting * :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: .. _format:
.. doxygenfunction:: format(const S&, Args&&...) .. 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: .. _print:
@ -104,7 +105,7 @@ Format API
========== ==========
``fmt/format.h`` defines the full format API providing compile-time format ``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 Compile-time Format String Checks
--------------------------------- ---------------------------------
@ -178,8 +179,7 @@ example::
enum class color {red, green, blue}; enum class color {red, green, blue};
template <> template <> struct fmt::formatter<color>: formatter<string_view> {
struct fmt::formatter<color>: formatter<string_view> {
// parse is inherited from formatter<string_view>. // parse is inherited from formatter<string_view>.
template <typename FormatContext> template <typename FormatContext>
auto format(color c, FormatContext& ctx) { 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:: You can also write a formatter for a hierarchy of classes::
#include <type_traits> #include <type_traits>
@ -399,7 +408,7 @@ formatting::
std::time_t t = std::time(nullptr); std::time_t t = std::time(nullptr);
// Prints "The date is 2016-04-29." (with the current date) // 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 The format string syntax is described in the documentation of
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_. `strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.

View File

@ -6,7 +6,7 @@ import errno, os, shutil, sys, tempfile
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
from distutils.version import LooseVersion 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): def pip_install(package, commit=None, **kwargs):
"Install package using pip." "Install package using pip."

View File

@ -81,8 +81,8 @@ The general form of a *standard format specifier* is:
sign: "+" | "-" | " " sign: "+" | "-" | " "
width: `integer` | "{" `arg_id` "}" width: `integer` | "{" `arg_id` "}"
precision: `integer` | "{" `arg_id` "}" precision: `integer` | "{" `arg_id` "}"
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s" type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s"
int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X" int_type: "b" | "B" | "d" | "o" | "x" | "X"
The *fill* character can be any Unicode code point other than ``'{'`` or The *fill* character can be any Unicode code point other than ``'{'`` or
``'}'``. The presence of a fill character is signaled by the character following ``'}'``. 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 .. ifconfig:: False
The ``','`` option signals the use of a comma for a thousands separator. 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. instead.
*width* is a decimal integer defining the minimum field width. If not *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"`` | | | ``'#'`` option with this type adds the prefix ``"0X"`` |
| | to the output value. | | | to the output value. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| ``'n'`` | Number. This is the same as ``'d'``, except that it uses | | ``'L'`` | Locale-specific format. This is the same as ``'d'``, |
| | the current locale setting to insert the appropriate | | | except that it uses the current locale setting to insert |
| | number separator characters. | | | the appropriate number separator characters. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| none | The same as ``'d'``. | | 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 | | | ``'E'`` if the number gets too large. The |
| | representations of infinity and NaN are uppercased, too. | | | representations of infinity and NaN are uppercased, too. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| ``'n'`` | Number. This is the same as ``'g'``, except that it uses | | ``'L'`` | Locale-specific format. This is the same as ``'g'``, |
| | the current locale setting to insert the appropriate | | | except that it uses the current locale setting to insert |
| | number separator characters. | | | the appropriate number separator characters. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| none | Similar to ``'g'``, except that fixed-point notation, | | none | Similar to ``'g'``, except that fixed-point notation, |
| | when used, has at least one digit past the decimal | | | 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> #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" // s == "1,234,567,890"
.. ifconfig:: False .. ifconfig:: False

View File

@ -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, return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
val); 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> template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) { OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>()) { if (const char* unit = get_units<Period>())
string_view s(unit); return copy_unit(string_view(unit), out, Char());
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);
}
const Char num_f[] = {'[', '{', '}', ']', 's', 0}; const Char num_f[] = {'[', '{', '}', ']', 's', 0};
if (Period::den == 1) return format_to(out, num_f, Period::num); if (Period::den == 1) return format_to(out, num_f, Period::num);
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};

View File

@ -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* begin() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
return buffer + std::strlen(buffer); return buffer + std::char_traits<Char>::length(buffer);
} }
private: private:

View File

@ -576,7 +576,9 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf,
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, 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, format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf, const CompiledFormat& cf,
const Args&... args) { const Args&... args) {

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,14 @@
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
# if defined(NOMINMAX)
# include <windows.h>
# else
# define NOMINMAX
# include <windows.h>
# undef NOMINMAX
# endif
# include <io.h> # include <io.h>
# include <windows.h>
#endif #endif
#ifdef _MSC_VER #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 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 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>::signs[] = {0, '-', '+', ' '};
template <typename T>
const char basic_data<T>::padding_shifts[] = {31, 31, 0, 1, 0};
template <typename T> struct bits { template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value = static FMT_CONSTEXPR_DECL const int value =
@ -507,20 +515,23 @@ class bigint {
basic_memory_buffer<bigit, bigits_capacity> bigits_; basic_memory_buffer<bigit, bigits_capacity> bigits_;
int exp_; 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; static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
friend struct formatter<bigint>; friend struct formatter<bigint>;
void subtract_bigits(int index, bigit other, bigit& borrow) { void subtract_bigits(int index, bigit other, bigit& borrow) {
auto result = static_cast<double_bigit>(bigits_[index]) - other - borrow; auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
bigits_[index] = static_cast<bigit>(result); (*this)[index] = static_cast<bigit>(result);
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1)); borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
} }
void remove_leading_zeros() { void remove_leading_zeros() {
int num_bigits = static_cast<int>(bigits_.size()) - 1; int num_bigits = static_cast<int>(bigits_.size()) - 1;
while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
bigits_.resize(num_bigits + 1); bigits_.resize(to_unsigned(num_bigits + 1));
} }
// Computes *this -= other assuming aligned bigints and *this >= other. // 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_; } int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
bigint& operator<<=(int shift) { FMT_NOINLINE bigint& operator<<=(int shift) {
assert(shift >= 0); assert(shift >= 0);
exp_ += shift / bigit_bits; exp_ += shift / bigit_bits;
shift %= bigit_bits; shift %= bigit_bits;
@ -621,7 +632,7 @@ class bigint {
int end = i - j; int end = i - j;
if (end < 0) end = 0; if (end < 0) end = 0;
for (; i >= end; --i, --j) { 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 (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1;
} }
if (i != j) return i > j ? 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 + 1 < num_rhs_bigits) return -1;
if (max_lhs_bigits > num_rhs_bigits) return 1; if (max_lhs_bigits > num_rhs_bigits) return 1;
auto get_bigit = [](const bigint& n, int i) -> bigit { 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; double_bigit borrow = 0;
int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); 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_)); basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
int num_bigits = static_cast<int>(bigits_.size()); int num_bigits = static_cast<int>(bigits_.size());
int num_result_bigits = 2 * num_bigits; 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>; using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>;
auto sum = accumulator_t(); auto sum = accumulator_t();
for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { 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. // Most terms are multiplied twice which can be optimized in the future.
sum += static_cast<double_bigit>(n[i]) * n[j]; 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. sum >>= bits<bigit>::value; // Compute the carry.
} }
// Do the same for the top half. // Do the same for the top half.
@ -694,7 +705,7 @@ class bigint {
++bigit_index) { ++bigit_index) {
for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;)
sum += static_cast<double_bigit>(n[i++]) * n[j--]; 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; sum >>= bits<bigit>::value;
} }
--num_result_bigits; --num_result_bigits;
@ -708,11 +719,11 @@ class bigint {
FMT_ASSERT(this != &divisor, ""); FMT_ASSERT(this != &divisor, "");
if (compare(*this, divisor) < 0) return 0; if (compare(*this, divisor) < 0) return 0;
int num_bigits = static_cast<int>(bigits_.size()); 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_; int exp_difference = exp_ - divisor.exp_;
if (exp_difference > 0) { if (exp_difference > 0) {
// Align bigints by adding trailing zeros to simplify subtraction. // 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) for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
bigits_[j] = bigits_[i]; bigits_[j] = bigits_[i];
std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); 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)) if (result > 0 || (result == 0 && (digit % 2) != 0))
++data[num_digits - 1]; ++data[num_digits - 1];
} }
buf.resize(num_digits); buf.resize(to_unsigned(num_digits));
exp10 -= num_digits - 1; exp10 -= num_digits - 1;
return; return;
} }
@ -1142,7 +1153,7 @@ int snprintf_float(T value, int precision, float_specs specs,
for (;;) { for (;;) {
auto begin = buf.data() + offset; auto begin = buf.data() + offset;
auto capacity = buf.capacity() - offset; auto capacity = buf.capacity() - offset;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FMT_FUZZ
if (precision > 100000) if (precision > 100000)
throw std::runtime_error( throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf"); "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. buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially.
continue; continue;
} }
unsigned size = to_unsigned(result); auto size = to_unsigned(result);
// Size equal to capacity means that the last character was truncated. // Size equal to capacity means that the last character was truncated.
if (size >= capacity) { if (size >= capacity) {
buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'. 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; --p;
} while (is_digit(*p)); } while (is_digit(*p));
int fraction_size = static_cast<int>(end - p - 1); 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); buf.resize(size - 1);
return -fraction_size; return -fraction_size;
} }
@ -1204,9 +1215,9 @@ int snprintf_float(T value, int precision, float_specs specs,
while (*fraction_end == '0') --fraction_end; while (*fraction_end == '0') --fraction_end;
// Move the fractional part left to get rid of the decimal point. // Move the fractional part left to get rid of the decimal point.
fraction_size = static_cast<int>(fraction_end - begin - 1); 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; return exp - fraction_size;
} }
} }
@ -1277,7 +1288,7 @@ template <> struct formatter<internal::bigint> {
auto out = ctx.out(); auto out = ctx.out();
bool first = true; bool first = true;
for (auto i = n.bigits_.size(); i > 0; --i) { for (auto i = n.bigits_.size(); i > 0; --i) {
auto value = n.bigits_[i - 1]; auto value = n.bigits_[i - 1u];
if (first) { if (first) {
out = format_to(out, "{:x}", value); out = format_to(out, "{:x}", value);
first = false; 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) { if (auto num_chars_left = s.data() + s.size() - p) {
char buf[2 * block_size - 1] = {}; char buf[2 * block_size - 1] = {};
memcpy(buf, p, num_chars_left); memcpy(buf, p, to_unsigned(num_chars_left));
p = buf; p = buf;
do { do {
p = transcode(p); 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)), if (!WriteConsoleW(reinterpret_cast<HANDLE>(_get_osfhandle(fd)),
u16.c_str(), static_cast<DWORD>(u16.size()), &written, u16.c_str(), static_cast<DWORD>(u16.size()), &written,
nullptr)) { nullptr)) {
throw format_error("failed to write to console"); FMT_THROW(format_error("failed to write to console"));
} }
return; return;
} }

File diff suppressed because it is too large Load Diff

View File

@ -15,11 +15,10 @@
#include <cerrno> #include <cerrno>
#include <clocale> // for locale_t #include <clocale> // for locale_t
#include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstdlib> // for strtod_l #include <cstdlib> // for strtod_l
#include <cstddef>
#if defined __APPLE__ || defined(__FreeBSD__) #if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X # include <xlocale.h> // for LC_NUMERIC_MASK on OS X
#endif #endif
@ -346,21 +345,15 @@ long getpagesize();
#ifdef FMT_LOCALE #ifdef FMT_LOCALE
// A "C" numeric locale. // A "C" numeric locale.
class Locale { class locale {
private: private:
# ifdef _WIN32 # ifdef _WIN32
using locale_t = _locale_t; 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) { static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
return _create_locale(category_mask, locale); return _strtod_l(nptr, endptr, loc);
}
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);
} }
# endif # endif
@ -368,13 +361,18 @@ class Locale {
public: public:
using type = locale_t; using type = locale_t;
Locale(const Locale&) = delete; locale(const locale&) = delete;
void operator=(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")); if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
} }
~Locale() { freelocale(locale_); } ~locale() { freelocale(locale_); }
type get() const { return locale_; } type get() const { return locale_; }
@ -387,6 +385,7 @@ class Locale {
return result; return result;
} }
}; };
using Locale FMT_DEPRECATED_ALIAS = locale;
#endif // FMT_LOCALE #endif // FMT_LOCALE
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -9,9 +9,14 @@
#define FMT_OSTREAM_H_ #define FMT_OSTREAM_H_
#include <ostream> #include <ostream>
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context;
namespace internal { namespace internal {
template <class Char> class formatbuf : public std::basic_streambuf<Char> { 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()) { locale_ref loc = locale_ref()) {
formatbuf<Char> format_buf(buf); formatbuf<Char> format_buf(buf);
std::basic_ostream<Char> output(&format_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>()); if (loc) output.imbue(loc.get<std::locale>());
#endif #endif
output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
output << value; output << value;
buf.resize(buf.size()); 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<<. // Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char> template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: formatter<basic_string_view<Char>, Char> { : private formatter<basic_string_view<Char>, Char> {
template <typename Context> auto parse(basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin()) {
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) { 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; basic_memory_buffer<Char> buffer;
format_value(buffer, value, ctx.locale()); format_value(buffer, value, ctx.locale());
basic_string_view<Char> str(buffer.data(), buffer.size()); basic_string_view<Char> str(buffer.data(), buffer.size());
return formatter<basic_string_view<Char>, Char>::format(str, ctx); 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 } // namespace internal

View File

@ -141,6 +141,13 @@ template <typename Context> class char_converter {
void operator()(T) {} // No conversion needed for non-integral types. 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 // Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative. // left alignment if it is negative.
template <typename Char> class printf_width_handler { template <typename Char> class printf_width_handler {
@ -172,23 +179,27 @@ template <typename Char> class printf_width_handler {
}; };
template <typename Char, typename Context> template <typename Char, typename Context>
void printf(buffer<Char>& buf, basic_string_view<Char> format, void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) { basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format(); 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 } // 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 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; 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 char_type = Char;
using iterator = OutputIt; using iterator = OutputIt;
using format_arg = basic_format_arg<basic_printf_context>; 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>; template <typename T> using formatter_type = printf_formatter<T>;
private: private:
@ -331,7 +343,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
OutputIt out_; OutputIt out_;
basic_format_args<basic_printf_context> args_; 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, static void parse_flags(format_specs& specs, const Char*& it,
const Char* end); 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); } 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) { FMT_CONSTEXPR void on_error(const char* message) {
parse_ctx_.on_error(message); parse_ctx_.on_error(message);
@ -471,7 +483,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
// Parse argument index, flags and width. // Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs); 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. // Parse precision.
if (it != end && *it == '.') { if (it != end && *it == '.') {
@ -490,6 +502,13 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
} }
format_arg arg = get_arg(arg_index); 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)) if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
specs.alt = false; specs.alt = false;
if (specs.fill[0] == '0') { if (specs.fill[0] == '0') {
@ -605,7 +624,7 @@ inline std::basic_string<Char> vsprintf(
const S& format, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args); vprintf(buffer, to_string_view(format), args);
return to_string(buffer); return to_string(buffer);
} }
@ -630,7 +649,7 @@ inline int vfprintf(
std::FILE* f, const S& format, std::FILE* f, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer; 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(); std::size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1 ? -1
@ -683,7 +702,7 @@ inline int vfprintf(
std::basic_ostream<Char>& os, const S& format, std::basic_ostream<Char>& os, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args); vprintf(buffer, to_string_view(format), args);
internal::write(os, buffer); internal::write(os, buffer);
return static_cast<int>(buffer.size()); return static_cast<int>(buffer.size());
} }

View File

@ -12,7 +12,9 @@
#ifndef FMT_RANGES_H_ #ifndef FMT_RANGES_H_
#define FMT_RANGES_H_ #define FMT_RANGES_H_
#include <initializer_list>
#include <type_traits> #include <type_traits>
#include "format.h" #include "format.h"
// output only up to N items from the range. // output only up to N items from the range.
@ -104,10 +106,7 @@ struct is_range_<
/// tuple_size and tuple_element check. /// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ { template <typename T> class is_tuple_like_ {
template <typename U> template <typename U>
static auto check(U* p) static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
-> decltype(std::tuple_size<U>::value,
(void)std::declval<typename std::tuple_element<0, U>::type>(),
int());
template <typename> static void check(...); template <typename> static void check(...);
public: public:
@ -360,6 +359,29 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
return {tuple, sep}; 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 FMT_END_NAMESPACE
#endif // FMT_RANGES_H_ #endif // FMT_RANGES_H_

View File

@ -13,7 +13,7 @@ namespace internal {
template <typename T> template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision, int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) { T value) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FMT_FUZZ
if (precision > 100000) if (precision > 100000)
throw std::runtime_error( throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf"); "fuzz mode - avoid large allocation inside snprintf");
@ -29,7 +29,7 @@ struct sprintf_specs {
bool alt : 1; bool alt : 1;
template <typename Char> 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) {} : precision(specs.precision), type(specs.type), alt(specs.alt) {}
constexpr bool has_precision() const { return precision >= 0; } 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); unsigned n = internal::to_unsigned(result);
if (n < buf.capacity()) { if (n < buf.capacity()) {
// Find the decimal point. // Find the decimal point.
auto p = buf.data(), end = p + n; auto* p = buf.data();
auto* end = p + n;
if (*p == '+' || *p == '-') ++p; if (*p == '+' || *p == '-') ++p;
if (specs.type != 'a' && specs.type != 'A') { if (specs.type != 'a' && specs.type != 'A') {
while (p < end && *p >= '0' && *p <= '9') ++p; 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; 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 } // namespace internal
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&, 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*, template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
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 FMT_END_NAMESPACE

View 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 Pythons 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()

View File

@ -1,7 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@ prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ libdir=@libdir_for_pc_file@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ includedir=@includedir_for_pc_file@
Name: fmt Name: fmt
Description: A modern formatting library Description: A modern formatting library

View File

@ -144,9 +144,15 @@ def update_site(env):
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data) 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('std::FILE*', 'std::FILE *')
b.data = b.data.replace('unsigned int', 'unsigned') 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(', size_t', ', std::size_t')
b.data = b.data.replace('aa long', 'a long') 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. # Fix a broken link in index.rst.
index = os.path.join(target_doc_dir, 'index.rst') index = os.path.join(target_doc_dir, 'index.rst')
with rewrite(index) as b: with rewrite(index) as b:

View File

@ -95,7 +95,6 @@ add_fmt_test(grisu-test)
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1) target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
add_fmt_test(gtest-extra-test) add_fmt_test(gtest-extra-test)
add_fmt_test(format-test mock-allocator.h) add_fmt_test(format-test mock-allocator.h)
add_fmt_test(format-dyn-args-test)
if (MSVC) if (MSVC)
target_compile_options(format-test PRIVATE /bigobj) target_compile_options(format-test PRIVATE /bigobj)
endif () endif ()

View File

@ -50,6 +50,8 @@ TEST(ColorsTest, ColorsPrint) {
TEST(ColorsTest, Format) { TEST(ColorsTest, Format) {
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), 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"); "\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"), EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m"); "\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_EQ( EXPECT_EQ(

View File

@ -15,9 +15,8 @@
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include "test-assert.h"
#include "gmock.h" #include "gmock.h"
#include "test-assert.h"
// Check if fmt/core.h compiles with windows.h included before it. // Check if fmt/core.h compiles with windows.h included before it.
#ifdef _WIN32 #ifdef _WIN32
@ -211,7 +210,8 @@ TEST(ArgTest, FormatArgs) {
} }
struct custom_context { 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 T> struct formatter_type {
template <typename ParseContext> template <typename ParseContext>
@ -402,6 +402,86 @@ TEST(ArgTest, VisitInvalidArg) {
fmt::visit_format_arg(visitor, arg); 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) { TEST(StringViewTest, ValueType) {
static_assert(std::is_same<string_view::value_type, char>::value, ""); 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 {}; struct non_string {};
} // namespace my_ns } // 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 {}; template <typename T> class IsStringTest : public testing::Test {};
typedef ::testing::Types<char, wchar_t, char16_t, char32_t> StringCharTypes; 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); fmt::internal::is_string<string_view>::value);
EXPECT_TRUE(fmt::internal::is_string<my_ns::my_string<TypeParam>>::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_FALSE(fmt::internal::is_string<my_ns::non_string>::value);
EXPECT_TRUE(fmt::internal::is_string<FakeQt::QString>::value);
} }
TEST(CoreTest, Format) { TEST(CoreTest, Format) {
@ -585,33 +647,16 @@ TEST(CoreTest, FormatTo) {
TEST(CoreTest, ToStringViewForeignStrings) { TEST(CoreTest, ToStringViewForeignStrings) {
using namespace my_ns; using namespace my_ns;
using namespace FakeQt;
EXPECT_EQ(to_string_view(my_string<char>("42")), "42"); 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::type type =
fmt::internal::mapped_type_constant<my_string<char>, fmt::internal::mapped_type_constant<my_string<char>,
fmt::format_context>::value; fmt::format_context>::value;
EXPECT_EQ(type, fmt::internal::type::string_type); 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) { TEST(CoreTest, FormatForeignStrings) {
using namespace my_ns; using namespace my_ns;
using namespace FakeQt;
EXPECT_EQ(fmt::format(my_string<char>("{}"), 42), "42"); 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 { struct implicitly_convertible_to_string {
@ -646,15 +691,6 @@ TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
fmt::format("{}", explicitly_convertible_to_std_string_view())); fmt::format("{}", explicitly_convertible_to_std_string_view()));
} }
# endif # 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 #endif
struct disabled_rvalue_conversion { struct disabled_rvalue_conversion {

View File

@ -4,84 +4,3 @@
#include <fmt/core.h> #include <fmt/core.h>
#include "gtest-extra.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);
}

View File

@ -10,12 +10,11 @@
#include "test-assert.h" #include "test-assert.h"
// Include format.cc instead of format.h to test implementation. // Include format.cc instead of format.h to test implementation.
#include "../src/format.cc"
#include "fmt/printf.h"
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include "../src/format.cc"
#include "fmt/printf.h"
#include "gmock.h" #include "gmock.h"
#include "gtest-extra.h" #include "gtest-extra.h"
#include "util.h" #include "util.h"
@ -430,8 +429,10 @@ TEST(FormatTest, CountCodePoints) {
#ifndef __cpp_char8_t #ifndef __cpp_char8_t
using fmt::char8_t; using fmt::char8_t;
#endif #endif
EXPECT_EQ(4, fmt::internal::count_code_points( EXPECT_EQ(
fmt::basic_string_view<char8_t>(reinterpret_cast<const char8_t*>("ёжик")))); 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. // Tests fmt::internal::count_digits for integer type Int.

View File

@ -46,6 +46,7 @@ using fmt::format_error;
using fmt::memory_buffer; using fmt::memory_buffer;
using fmt::string_view; using fmt::string_view;
using fmt::wmemory_buffer; using fmt::wmemory_buffer;
using fmt::wstring_view;
using fmt::internal::basic_writer; using fmt::internal::basic_writer;
using fmt::internal::max_value; 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("{?}"), 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, "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"); EXPECT_THROW_MSG(format("{00}", 42), format_error, "invalid format string");
char format_str[BUFFER_SIZE]; char format_str[BUFFER_SIZE];
safe_sprintf(format_str, "{%u", INT_MAX); safe_sprintf(format_str, "{%u", INT_MAX);
EXPECT_THROW_MSG(format(format_str), format_error, "invalid format string"); EXPECT_THROW_MSG(format(format_str), format_error, "invalid format string");
safe_sprintf(format_str, "{%u}", INT_MAX); safe_sprintf(format_str, "{%u}", INT_MAX);
EXPECT_THROW_MSG(format(format_str), format_error, EXPECT_THROW_MSG(format(format_str), format_error, "argument not found");
"argument index out of range");
safe_sprintf(format_str, "{%u", INT_MAX + 1u); safe_sprintf(format_str, "{%u", INT_MAX + 1u);
EXPECT_THROW_MSG(format(format_str), format_error, "number is too big"); EXPECT_THROW_MSG(format(format_str), format_error, "number is too big");
@ -672,13 +672,13 @@ template <> struct TestFormat<0> {
TEST(FormatterTest, ManyArgs) { TEST(FormatterTest, ManyArgs) {
EXPECT_EQ("19", TestFormat<20>::format("{19}")); EXPECT_EQ("19", TestFormat<20>::format("{19}"));
EXPECT_THROW_MSG(TestFormat<20>::format("{20}"), format_error, 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, 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 }; enum { max_packed_args = fmt::internal::max_packed_args };
std::string format_str = fmt::format("{{{}}}", max_packed_args + 1); std::string format_str = fmt::format("{{{}}}", max_packed_args + 1);
EXPECT_THROW_MSG(TestFormat<max_packed_args>::format(format_str), 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) { TEST(FormatterTest, NamedArg) {
@ -707,7 +707,7 @@ TEST(FormatterTest, AutoArgIndex) {
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(format("{:.{0}}", 1.2345, 2), format_error, EXPECT_THROW_MSG(format("{:.{0}}", 1.2345, 2), format_error,
"cannot switch from automatic to manual argument indexing"); "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)); } TEST(FormatterTest, EmptySpecs) { EXPECT_EQ("42", format("{0:}", 42)); }
@ -1010,8 +1010,7 @@ TEST(FormatterTest, RuntimeWidth) {
EXPECT_THROW_MSG(format("{0:{}", 0), format_error, EXPECT_THROW_MSG(format("{0:{}", 0), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(format("{0:{?}}", 0), format_error, "invalid format string"); EXPECT_THROW_MSG(format("{0:{?}}", 0), format_error, "invalid format string");
EXPECT_THROW_MSG(format("{0:{1}}", 0), format_error, EXPECT_THROW_MSG(format("{0:{1}}", 0), format_error, "argument not found");
"argument index out of range");
EXPECT_THROW_MSG(format("{0:{0:}}", 0), format_error, EXPECT_THROW_MSG(format("{0:{0:}}", 0), format_error,
"invalid format string"); "invalid format string");
@ -1124,6 +1123,8 @@ TEST(FormatterTest, Precision) {
"000000000000000000000000000000000000000000000000000P+127", "000000000000000000000000000000000000000000000000000P+127",
format("{:.838A}", -2.14001164E+38)); format("{:.838A}", -2.14001164E+38));
EXPECT_EQ("123.", format("{:#.0f}", 123.0)); 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)), EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast<void*>(0xcafe)),
format_error, format_error,
@ -1157,8 +1158,7 @@ TEST(FormatterTest, RuntimePrecision) {
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(format("{0:.{1}", 0, 0), format_error, EXPECT_THROW_MSG(format("{0:.{1}", 0, 0), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(format("{0:.{1}}", 0), format_error, EXPECT_THROW_MSG(format("{0:.{1}}", 0), format_error, "argument not found");
"argument index out of range");
EXPECT_THROW_MSG(format("{0:.{0:}}", 0), format_error, EXPECT_THROW_MSG(format("{0:.{0:}}", 0), format_error,
"invalid format string"); "invalid format string");
@ -1253,7 +1253,7 @@ TEST(FormatterTest, FormatShort) {
TEST(FormatterTest, FormatInt) { TEST(FormatterTest, FormatInt) {
EXPECT_THROW_MSG(format("{0:v", 42), format_error, EXPECT_THROW_MSG(format("{0:v", 42), format_error,
"missing '}' in format string"); "missing '}' in format string");
check_unknown_types(42, "bBdoxXn", "integer"); check_unknown_types(42, "bBdoxXnL", "integer");
} }
TEST(FormatterTest, FormatBin) { TEST(FormatterTest, FormatBin) {
@ -1394,6 +1394,7 @@ TEST(FormatterTest, FormatOct) {
TEST(FormatterTest, FormatIntLocale) { TEST(FormatterTest, FormatIntLocale) {
EXPECT_EQ("1234", format("{:n}", 1234)); EXPECT_EQ("1234", format("{:n}", 1234));
EXPECT_EQ("1234", format("{:L}", 1234));
} }
struct ConvertibleToLongLong { struct ConvertibleToLongLong {
@ -1409,7 +1410,7 @@ TEST(FormatterTest, FormatFloat) {
} }
TEST(FormatterTest, FormatDouble) { 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.0", format("{:}", 0.0));
EXPECT_EQ("0.000000", format("{:f}", 0.0)); EXPECT_EQ("0.000000", format("{:f}", 0.0));
EXPECT_EQ("0", format("{:g}", 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.65", format("{:G}", 392.65));
EXPECT_EQ("392.650000", format("{:f}", 392.65)); EXPECT_EQ("392.650000", format("{:f}", 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]; char buffer[BUFFER_SIZE];
safe_sprintf(buffer, "%e", 392.65); safe_sprintf(buffer, "%e", 392.65);
EXPECT_EQ(buffer, format("{0:e}", 392.65)); EXPECT_EQ(buffer, format("{0:e}", 392.65));
@ -1488,7 +1490,7 @@ TEST(FormatterTest, FormatLongDouble) {
} }
TEST(FormatterTest, FormatChar) { TEST(FormatterTest, FormatChar) {
const char types[] = "cbBdoxXn"; const char types[] = "cbBdoxXnL";
check_unknown_types('a', types, "char"); check_unknown_types('a', types, "char");
EXPECT_EQ("a", format("{0}", 'a')); EXPECT_EQ("a", format("{0}", 'a'));
EXPECT_EQ("z", format("{0:c}", 'z')); EXPECT_EQ("z", format("{0:c}", 'z'));
@ -1604,6 +1606,40 @@ TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
} }
#endif #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 FMT_BEGIN_NAMESPACE
template <> struct formatter<Date> { template <> struct formatter<Date> {
template <typename ParseContext> template <typename ParseContext>
@ -1754,6 +1790,8 @@ TEST(FormatTest, Print) {
EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"), EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
"Don't panic!"); "Don't panic!");
#endif #endif
// Check that the wide print overload compiles.
if (fmt::internal::const_check(false)) fmt::print(L"test");
} }
TEST(FormatTest, Variadic) { TEST(FormatTest, Variadic) {
@ -1852,10 +1890,23 @@ TEST(FormatTest, UnpackedArgs) {
struct string_like {}; struct string_like {};
fmt::string_view to_string_view(string_like) { return "foo"; } fmt::string_view to_string_view(string_like) { return "foo"; }
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
TEST(FormatTest, CompileTimeString) { TEST(FormatTest, CompileTimeString) {
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42)); EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42));
EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like())); 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) { TEST(FormatTest, CustomFormatCompileTimeString) {
@ -2240,8 +2291,9 @@ struct test_parse_context {
}; };
struct test_context { struct test_context {
typedef char char_type; using char_type = char;
typedef fmt::basic_format_arg<test_context> format_arg; using format_arg = fmt::basic_format_arg<test_context>;
using parse_context_type = fmt::format_parse_context;
template <typename T> struct formatter_type { template <typename T> struct formatter_type {
typedef fmt::formatter<T, char_type> 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> template <typename... Args>
FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) { FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
const char* actual_error = nullptr; const char* actual_error = nullptr;
fmt::internal::do_check_format_string<char, test_error_handler, Args...>( string_view s(fmt, len(fmt));
string_view(fmt, len(fmt)), test_error_handler(actual_error)); 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); return equal(actual_error, expected_error);
} }
@ -2415,12 +2469,12 @@ TEST(FormatTest, FormatStringErrors) {
EXPECT_ERROR_NOARGS("foo", nullptr); EXPECT_ERROR_NOARGS("foo", nullptr);
EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string"); EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string");
EXPECT_ERROR("{0:s", "unknown format specifier", Date); 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. // This causes an internal compiler error in MSVC2017.
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int); EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
EXPECT_ERROR("{:10000000000}", "number is too big", int); EXPECT_ERROR("{:10000000000}", "number is too big", 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 # if FMT_NUMERIC_ALIGN
EXPECT_ERROR("{0:=5", "unknown format specifier", int); EXPECT_ERROR("{0:=5", "unknown format specifier", int);
EXPECT_ERROR("{:=}", "format specifier requires numeric argument", 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("{:-}", "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("{:.2}", "precision not allowed for this argument type", int);
EXPECT_ERROR("{:s}", "invalid type specifier", int); EXPECT_ERROR("{:s}", "invalid type specifier", int);
EXPECT_ERROR("{:s}", "invalid type specifier", bool); EXPECT_ERROR("{:s}", "invalid type specifier", bool);
@ -2461,8 +2517,8 @@ TEST(FormatTest, FormatStringErrors) {
EXPECT_ERROR("{:.{0x}}", "invalid format string", int); EXPECT_ERROR("{:.{0x}}", "invalid format string", int);
EXPECT_ERROR("{:.{-}}", "invalid format string", int); EXPECT_ERROR("{:.{-}}", "invalid format string", int);
EXPECT_ERROR("{:.x}", "missing precision specifier", int); EXPECT_ERROR("{:.x}", "missing precision specifier", int);
EXPECT_ERROR_NOARGS("{}", "argument index out of range"); EXPECT_ERROR_NOARGS("{}", "argument not found");
EXPECT_ERROR("{1}", "argument index out of range", int); EXPECT_ERROR("{1}", "argument not found", int);
EXPECT_ERROR("{1}{}", EXPECT_ERROR("{1}{}",
"cannot switch from manual to automatic argument indexing", int, "cannot switch from manual to automatic argument indexing", int,
int); int);
@ -2504,25 +2560,6 @@ TEST(FormatTest, FmtStringInTemplate) {
#endif // FMT_USE_CONSTEXPR #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) { TEST(FormatTest, EmphasisNonHeaderOnly) {
// Ensure this compiles even if FMT_HEADER_ONLY is not defined. // Ensure this compiles even if FMT_HEADER_ONLY is not defined.
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"),
@ -2561,10 +2598,18 @@ TEST(FormatTest, FormatCustomChar) {
EXPECT_EQ(result[0], mychar('x')); 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) { TEST(FormatTest, FormatUTF8Precision) {
using str_type = std::basic_string<char8_t>; using str_type = std::basic_string<fmt::internal::char8_type>;
str_type format(reinterpret_cast<const char8_t*>(u8"{:.4}")); str_type format(
str_type str(reinterpret_cast<const char8_t*>(u8"caf\u00e9s")); // cafés 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); auto result = fmt::format(format, str);
EXPECT_EQ(fmt::internal::count_code_points(result), 4); EXPECT_EQ(fmt::internal::count_code_points(result), 4);
EXPECT_EQ(result.size(), 5); EXPECT_EQ(result.size(), 5);

View File

@ -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 The source code is modified to make the fuzzing possible without locking up on
resource exhaustion: resource exhaustion:
```cpp ```cpp
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FMT_FUZZ
if(spec.precision>100000) { if(spec.precision>100000) {
throw std::runtime_error("fuzz mode - avoiding large precision"); throw std::runtime_error("fuzz mode - avoiding large precision");
} }
#endif #endif
``` ```
This macro is the defacto standard for making fuzzing practically possible, see This macro `FMT_FUZZ` is enabled on OSS-Fuzz builds and makes fuzzing
[the libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode). 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 ## Running the fuzzers locally

View File

@ -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 // allocating buffers separately is slower, but increases chances
// of detecting memory errors // of detecting memory errors
#if FMT_FUZZ_SEPARATE_ALLOCATION #if FMT_FUZZ_SEPARATE_ALLOCATION
std::vector<char> argnamebuffer(argsize); std::vector<char> argnamebuffer(argsize + 1);
std::memcpy(argnamebuffer.data(), Data, argsize); std::memcpy(argnamebuffer.data(), Data, argsize);
auto argname = fmt::string_view(argnamebuffer.data(), argsize); auto argname = argnamebuffer.data();
#else #else
auto argname = fmt::string_view(fmt_fuzzer::as_chars(Data), argsize); auto argname = fmt_fuzzer::as_chars(Data);
#endif #endif
Data += argsize; Data += argsize;
Size -= argsize; Size -= argsize;

View File

@ -489,9 +489,9 @@ TEST(FileTest, Fdopen) {
# ifdef FMT_LOCALE # ifdef FMT_LOCALE
TEST(LocaleTest, Strtod) { TEST(LocaleTest, Strtod) {
fmt::Locale locale; fmt::locale loc;
const char *start = "4.2", *ptr = start; 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); EXPECT_EQ(start + 3, ptr);
} }
# endif # endif

View File

@ -11,13 +11,15 @@
#endif #endif
#include "posix-mock.h" #include "posix-mock.h"
#include "../src/os.cc"
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <climits> #include <climits>
#include <memory> #include <memory>
#include "../src/os.cc"
#ifdef _WIN32 #ifdef _WIN32
# include <io.h> # include <io.h>
# undef max # undef max
@ -51,7 +53,7 @@ std::size_t read_nbyte;
std::size_t write_nbyte; std::size_t write_nbyte;
bool sysconf_error; bool sysconf_error;
enum FStatSimulation { NONE, MAX_SIZE, ERROR } fstat_sim; enum { NONE, MAX_SIZE, ERROR } fstat_sim;
} // namespace } // namespace
#define EMULATE_EINTR(func, error_result) \ #define EMULATE_EINTR(func, error_result) \
@ -215,10 +217,10 @@ TEST(UtilTest, GetPageSize) {
} }
TEST(FileTest, OpenRetry) { 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}; std::unique_ptr<file> f{nullptr};
EXPECT_RETRY(f.reset(new file("test", file::RDONLY)), open, EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
"cannot open file test"); "cannot open file temp");
# ifndef _WIN32 # ifndef _WIN32
char c = 0; char c = 0;
f->read(&c, 1); f->read(&c, 1);
@ -230,14 +232,15 @@ TEST(FileTest, CloseNoRetryInDtor) {
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
std::unique_ptr<file> f(new file(std::move(read_end))); std::unique_ptr<file> f(new file(std::move(read_end)));
int saved_close_count = 0; int saved_close_count = 0;
EXPECT_WRITE(stderr, EXPECT_WRITE(
{ stderr,
close_count = 1; {
f.reset(nullptr); close_count = 1;
saved_close_count = close_count; f.reset(nullptr);
close_count = 0; saved_close_count = close_count;
}, close_count = 0;
format_system_error(EINTR, "cannot close file") + "\n"); },
format_system_error(EINTR, "cannot close file") + "\n");
EXPECT_EQ(2, saved_close_count); EXPECT_EQ(2, saved_close_count);
} }
@ -252,8 +255,8 @@ TEST(FileTest, CloseNoRetry) {
TEST(FileTest, Size) { TEST(FileTest, Size) {
std::string content = "top secret, destroy before reading"; std::string content = "top secret, destroy before reading";
write_file("test", content); write_file("temp", content);
file f("test", file::RDONLY); file f("temp", file::RDONLY);
EXPECT_GE(f.size(), 0); EXPECT_GE(f.size(), 0);
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size())); EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
# ifdef _WIN32 # ifdef _WIN32
@ -270,8 +273,8 @@ TEST(FileTest, Size) {
} }
TEST(FileTest, MaxSize) { TEST(FileTest, MaxSize) {
write_file("test", ""); write_file("temp", "");
file f("test", file::RDONLY); file f("temp", file::RDONLY);
fstat_sim = MAX_SIZE; fstat_sim = MAX_SIZE;
EXPECT_GE(f.size(), 0); EXPECT_GE(f.size(), 0);
EXPECT_EQ(max_file_size(), f.size()); EXPECT_EQ(max_file_size(), f.size());
@ -385,10 +388,10 @@ TEST(FileTest, FdopenNoRetry) {
} }
TEST(BufferedFileTest, OpenRetry) { 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}; std::unique_ptr<buffered_file> f{nullptr};
EXPECT_RETRY(f.reset(new buffered_file("test", "r")), fopen, EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
"cannot open file test"); "cannot open file temp");
# ifndef _WIN32 # ifndef _WIN32
char c = 0; char c = 0;
if (fread(&c, 1, 1, f->get()) < 1) if (fread(&c, 1, 1, f->get()) < 1)
@ -401,14 +404,15 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) {
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r"))); std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
int saved_fclose_count = 0; int saved_fclose_count = 0;
EXPECT_WRITE(stderr, EXPECT_WRITE(
{ stderr,
fclose_count = 1; {
f.reset(nullptr); fclose_count = 1;
saved_fclose_count = fclose_count; f.reset(nullptr);
fclose_count = 0; saved_fclose_count = fclose_count;
}, fclose_count = 0;
format_system_error(EINTR, "cannot close file") + "\n"); },
format_system_error(EINTR, "cannot close file") + "\n");
EXPECT_EQ(2, saved_fclose_count); EXPECT_EQ(2, saved_fclose_count);
} }
@ -433,33 +437,33 @@ TEST(BufferedFileTest, FilenoNoRetry) {
} }
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
struct TestMock { struct test_mock {
static TestMock* instance; static test_mock* instance;
} * TestMock::instance; } * test_mock::instance;
TEST(ScopedMock, Scope) { TEST(ScopedMock, Scope) {
{ {
ScopedMock<TestMock> mock; ScopedMock<test_mock> mock;
EXPECT_EQ(&mock, TestMock::instance); EXPECT_EQ(&mock, test_mock::instance);
TestMock& copy = mock; test_mock& copy = mock;
static_cast<void>(copy); static_cast<void>(copy);
} }
EXPECT_EQ(nullptr, TestMock::instance); EXPECT_EQ(nullptr, test_mock::instance);
} }
#ifdef FMT_LOCALE #ifdef FMT_LOCALE
typedef fmt::Locale::type LocaleType; typedef fmt::locale::type locale_type;
struct LocaleMock { struct locale_mock {
static LocaleMock* instance; static locale_mock* instance;
MOCK_METHOD3(newlocale, LocaleType(int category_mask, const char* locale, MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale,
LocaleType base)); locale_type base));
MOCK_METHOD1(freelocale, void(LocaleType locale)); MOCK_METHOD1(freelocale, void(locale_type locale));
MOCK_METHOD3(strtod_l, MOCK_METHOD3(strtod_l,
double(const char* nptr, char** endptr, LocaleType locale)); double(const char* nptr, char** endptr, locale_type locale));
} * LocaleMock::instance; } * locale_mock::instance;
# ifdef _MSC_VER # ifdef _MSC_VER
# pragma warning(push) # pragma warning(push)
@ -470,15 +474,15 @@ struct LocaleMock {
# endif # endif
_locale_t _create_locale(int category, const char* locale) { _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) { 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) { 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__ # ifdef __clang__
# pragma clang diagnostic pop # pragma clang diagnostic pop
@ -492,11 +496,6 @@ double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
# define FMT_LOCALE_THROW # define FMT_LOCALE_THROW
# endif # 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__) || \ # if defined(__APPLE__) || \
(defined(__FreeBSD__) && __FreeBSD_version < 1200002) (defined(__FreeBSD__) && __FreeBSD_version < 1200002)
typedef int FreeLocaleResult; typedef int FreeLocaleResult;
@ -504,49 +503,55 @@ typedef int FreeLocaleResult;
typedef void FreeLocaleResult; typedef void FreeLocaleResult;
# endif # endif
FreeLocaleResult freelocale(LocaleType locale) FMT_LOCALE_THROW { FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW {
LocaleMock::instance->freelocale(locale); locale_mock::instance->freelocale(locale);
return FreeLocaleResult(); return FreeLocaleResult();
} }
double strtod_l(const char* nptr, char** endptr, double strtod_l(const char* nptr, char** endptr,
LocaleType locale) FMT_LOCALE_THROW { locale_type locale) FMT_LOCALE_THROW {
return LocaleMock::instance->strtod_l(nptr, endptr, locale); return locale_mock::instance->strtod_l(nptr, endptr, locale);
} }
# undef FMT_LOCALE_THROW # undef FMT_LOCALE_THROW
TEST(LocaleTest, LocaleMock) { # ifndef _WIN32
ScopedMock<LocaleMock> mock; locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
LocaleType locale = reinterpret_cast<LocaleType>(11); return locale_mock::instance->newlocale(category_mask, locale, base);
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
newlocale(222, "foo", locale);
} }
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) { TEST(LocaleTest, Locale) {
# ifndef LC_NUMERIC_MASK # ifndef LC_NUMERIC_MASK
enum { LC_NUMERIC_MASK = LC_NUMERIC }; enum { LC_NUMERIC_MASK = LC_NUMERIC };
# endif # endif
ScopedMock<LocaleMock> mock; ScopedMock<locale_mock> mock;
LocaleType impl = reinterpret_cast<LocaleType>(42); locale_type impl = reinterpret_cast<locale_type>(42);
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr)) EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
.WillOnce(Return(impl)); .WillOnce(Return(impl));
EXPECT_CALL(mock, freelocale(impl)); EXPECT_CALL(mock, freelocale(impl));
fmt::Locale locale; fmt::locale loc;
EXPECT_EQ(impl, locale.get()); EXPECT_EQ(impl, loc.get());
} }
TEST(LocaleTest, Strtod) { TEST(LocaleTest, Strtod) {
ScopedMock<LocaleMock> mock; ScopedMock<locale_mock> mock;
EXPECT_CALL(mock, newlocale(_, _, _)) EXPECT_CALL(mock, newlocale(_, _, _))
.WillOnce(Return(reinterpret_cast<LocaleType>(42))); .WillOnce(Return(reinterpret_cast<locale_type>(42)));
EXPECT_CALL(mock, freelocale(_)); EXPECT_CALL(mock, freelocale(_));
fmt::Locale locale; fmt::locale loc;
const char* str = "4.2"; const char* str = "4.2";
char end = 'x'; 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))); .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); EXPECT_EQ(&end, str);
} }

View File

@ -9,7 +9,11 @@
#define FMT_POSIX_TEST_H #define FMT_POSIX_TEST_H
#include <errno.h> #include <errno.h>
#include <locale.h>
#include <stdio.h> #include <stdio.h>
#ifdef __APPLE__
# include <xlocale.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
@ -62,6 +66,10 @@ int pipe(int* pfds, unsigned psize, int textmode);
FILE* fopen(const char* filename, const char* mode); FILE* fopen(const char* filename, const char* mode);
int fclose(FILE* stream); int fclose(FILE* stream);
int(fileno)(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 } // namespace test
#define FMT_SYSTEM(call) test::call #define FMT_SYSTEM(call) test::call

View File

@ -113,14 +113,14 @@ TEST(PrintfTest, SwitchArgIndexing) {
TEST(PrintfTest, InvalidArgIndex) { TEST(PrintfTest, InvalidArgIndex) {
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error, 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, 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, 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, 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, EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM), 42), format_error,
"number is too big"); "number is too big");
} }
@ -223,7 +223,7 @@ TEST(PrintfTest, DynamicWidth) {
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error, EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
"width is not integer"); "width is not integer");
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, 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, EXPECT_THROW_MSG(test_sprintf("%*d", BIG_NUM, 42), format_error,
"number is too big"); "number is too big");
} }
@ -259,6 +259,11 @@ TEST(PrintfTest, FloatPrecision) {
EXPECT_PRINTF(buffer, "%.3a", 1234.5678); 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) { TEST(PrintfTest, IgnorePrecisionForNonNumericArg) {
EXPECT_PRINTF("abc", "%.5s", "abc"); EXPECT_PRINTF("abc", "%.5s", "abc");
} }
@ -269,7 +274,7 @@ TEST(PrintfTest, DynamicPrecision) {
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error, EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
"precision is not integer"); "precision is not integer");
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, 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, EXPECT_THROW_MSG(test_sprintf("%.*d", BIG_NUM, 42), format_error,
"number is too big"); "number is too big");
if (sizeof(long long) != sizeof(int)) { if (sizeof(long long) != sizeof(int)) {
@ -447,6 +452,12 @@ TEST(PrintfTest, String) {
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr); 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) { TEST(PrintfTest, Pointer) {
int n; int n;
void* p = &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, WideString) { EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); }
TEST(PrintfTest, PrintfCustom) { TEST(PrintfTest, PrintfCustom) {
// The test is disabled for now because it requires decoupling EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
// fallback_formatter::format from format_context.
//EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
} }
TEST(PrintfTest, OStream) { TEST(PrintfTest, OStream) {

View File

@ -10,6 +10,7 @@
// {fmt} support for ranges, containers and types tuple interface. // {fmt} support for ranges, containers and types tuple interface.
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "gtest.h" #include "gtest.h"
// Check if 'if constexpr' is supported. // Check if 'if constexpr' is supported.
@ -44,9 +45,10 @@ TEST(RangesTest, FormatPair) {
} }
TEST(RangesTest, FormatTuple) { TEST(RangesTest, FormatTuple) {
std::tuple<int64_t, float, std::string, char> tu1{42, 1.5f, "this is tuple", std::tuple<int64_t, float, std::string, char> t{42, 1.5f, "this is tuple",
'i'}; 'i'};
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1)); EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t));
EXPECT_EQ("()", fmt::format("{}", std::tuple<>()));
} }
TEST(RangesTest, JoinTuple) { TEST(RangesTest, JoinTuple) {
@ -68,6 +70,12 @@ TEST(RangesTest, JoinTuple) {
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/"))); 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 { struct my_struct {
int32_t i; int32_t i;
std::string str; // can throw std::string str; // can throw