mirror of
https://github.com/fmtlib/fmt.git
synced 2025-12-23 15:28:27 +01:00
Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
398343897f | ||
|
|
749ed39d79 | ||
|
|
aba72982df | ||
|
|
e8ef103799 | ||
|
|
5e23fff052 | ||
|
|
f61e71ccb9 | ||
|
|
d9f0c58c65 | ||
|
|
569c5bdbf1 | ||
|
|
eafc6a3292 | ||
|
|
dc8c943372 | ||
|
|
ac5484c4e7 | ||
|
|
746adc5e71 | ||
|
|
048d2aec27 | ||
|
|
e14bac62a0 | ||
|
|
0284a2893a | ||
|
|
07ed421521 | ||
|
|
c56d345985 | ||
|
|
dcfd40a1b8 | ||
|
|
79f11dbaa7 | ||
|
|
52aabbe7ef | ||
|
|
4423490d0b | ||
|
|
d49f206183 | ||
|
|
7a4ac9ec9c | ||
|
|
82bb4f4e89 | ||
|
|
73ca9948fe | ||
|
|
cbac016cce | ||
|
|
c03f55ec3a | ||
|
|
fbc8ea8c3e | ||
|
|
6c3aa28c94 | ||
|
|
e3b5d806a8 | ||
|
|
cc736e7611 | ||
|
|
789ebea863 | ||
|
|
6f8006c2ce | ||
|
|
589b93de45 | ||
|
|
db0d54f855 | ||
|
|
703960aa54 | ||
|
|
e208fbff52 | ||
|
|
20089c23ca | ||
|
|
ad6d78f2a8 | ||
|
|
a00006119f | ||
|
|
bc6af7548b | ||
|
|
67662702aa | ||
|
|
9e9ad57f58 | ||
|
|
a2596d685d | ||
|
|
d8754af063 | ||
|
|
e0251fdcef | ||
|
|
e1689cb985 | ||
|
|
b9aaa507fc | ||
|
|
02553d13a0 | ||
|
|
db780cb119 | ||
|
|
fac5546321 | ||
|
|
796beaaddb | ||
|
|
def687462c | ||
|
|
25f8ad13dd | ||
|
|
1d2049b53e | ||
|
|
f5b1c16e2c | ||
|
|
cfd00af37a | ||
|
|
6274401919 | ||
|
|
2fa4655af6 | ||
|
|
8cef95f7c3 | ||
|
|
922ae4896b | ||
|
|
05ba3e7888 | ||
|
|
1daddec151 | ||
|
|
d8867a2b07 | ||
|
|
6c0125785b | ||
|
|
49ccb2e449 | ||
|
|
7ce7def515 | ||
|
|
8f455c10b0 | ||
|
|
e599fe7436 | ||
|
|
c577f72596 | ||
|
|
15d0f32773 | ||
|
|
8c63ea432c | ||
|
|
abbefd7166 | ||
|
|
ed874df293 | ||
|
|
1e018e65cb | ||
|
|
1500f00525 | ||
|
|
4809e2956a | ||
|
|
61fb85618c | ||
|
|
88c4bc33d2 | ||
|
|
cee50b7572 | ||
|
|
f66d37fb1c | ||
|
|
1546b9e336 | ||
|
|
17c17d1585 | ||
|
|
d09dd1a81a | ||
|
|
64a0016680 | ||
|
|
9ec3bea2d6 | ||
|
|
1fb0586b06 | ||
|
|
5f26b5da28 | ||
|
|
0d25f6fcbb | ||
|
|
2ae6bca488 | ||
|
|
04335aeadb | ||
|
|
3d5125cd87 | ||
|
|
d775a20fff | ||
|
|
4dc9fd995f | ||
|
|
a79c7b4e8f | ||
|
|
195d6a5663 | ||
|
|
6c184efa75 | ||
|
|
93975c70bb | ||
|
|
53f9e7f65c | ||
|
|
768739c310 | ||
|
|
94d387cd45 | ||
|
|
721c9100f2 | ||
|
|
108498bdd0 | ||
|
|
f19d8f9655 | ||
|
|
2bf59a97c6 | ||
|
|
12a6027b40 | ||
|
|
a5d0adf395 | ||
|
|
1a23f9c274 | ||
|
|
9dbb60c4c8 | ||
|
|
ed30108918 | ||
|
|
d5893c9a13 | ||
|
|
6ee9f2ed09 | ||
|
|
631ffef438 | ||
|
|
355861f1ff | ||
|
|
ab054532ce | ||
|
|
9823675832 | ||
|
|
d4ddaaf2b1 | ||
|
|
fa0f870ac9 | ||
|
|
1dbc6bd1fc | ||
|
|
c8c9973669 | ||
|
|
65cd664195 | ||
|
|
c110c6eca7 | ||
|
|
1acfd07f1e | ||
|
|
d4885cea62 | ||
|
|
903357c853 | ||
|
|
88b84d6429 | ||
|
|
d7c4e1cb1f | ||
|
|
0d5ef5c2a6 | ||
|
|
8631694021 | ||
|
|
4133e501f3 | ||
|
|
0e6df7e511 | ||
|
|
44c926d96f | ||
|
|
75f862bf57 | ||
|
|
116914a949 | ||
|
|
689fd2ad49 | ||
|
|
7a0806a366 | ||
|
|
a81bff9349 | ||
|
|
5c876088d3 | ||
|
|
d688072990 | ||
|
|
aa1e6b0f75 | ||
|
|
26819461bd | ||
|
|
2e69e454d0 | ||
|
|
a914384bc4 | ||
|
|
41356aa00a | ||
|
|
e9a0d3141b | ||
|
|
c7d0241abb | ||
|
|
e0d6f630f8 | ||
|
|
a201c61977 | ||
|
|
4569b4dbd6 | ||
|
|
c9bb5468b6 | ||
|
|
b26e76efe9 | ||
|
|
c7739536e8 | ||
|
|
dfa631e64a | ||
|
|
18e3f16576 | ||
|
|
72d51e0b1e | ||
|
|
6ccb5673c4 | ||
|
|
a82026746d | ||
|
|
9d36a5a3b0 | ||
|
|
81e2dac955 | ||
|
|
5940431e2d | ||
|
|
15008bf05d | ||
|
|
06045cb4a5 | ||
|
|
9492b9ff03 | ||
|
|
0d9870dd9e | ||
|
|
5e70843a73 | ||
|
|
d25e07660e | ||
|
|
579736f210 | ||
|
|
8650c57ccd | ||
|
|
9071daebe9 | ||
|
|
fcaf8a0cdc | ||
|
|
231c16df25 | ||
|
|
971359997f | ||
|
|
f7abf9fd0f | ||
|
|
e68836c123 | ||
|
|
dc1e36fa0e | ||
|
|
7bb1b50835 | ||
|
|
96c28f748d | ||
|
|
e160c2b79a | ||
|
|
e2a332e5df | ||
|
|
ebff26f8f1 | ||
|
|
559739ec1d | ||
|
|
45a1509455 | ||
|
|
5b106083e7 | ||
|
|
2d8a7ed086 | ||
|
|
b18f783fed | ||
|
|
b6c0cf9683 | ||
|
|
d00b43c592 | ||
|
|
cc9b051d12 | ||
|
|
d67eb8af2f | ||
|
|
3400e0d609 | ||
|
|
f76583276a | ||
|
|
31a4f0ab05 | ||
|
|
744c2824c5 | ||
|
|
140f11190b | ||
|
|
fb67a2f660 | ||
|
|
d2b65f77e8 | ||
|
|
03cdd4ca50 |
3
.github/pull_request_template
vendored
Normal file
3
.github/pull_request_template
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<!---
|
||||
Please make sure you've followed the guidelines outlined in the CONTRIBUTING.rst file.
|
||||
--->
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,15 +2,16 @@ bin/
|
||||
/_CPack_Packages
|
||||
/doc/doxyxml
|
||||
/doc/html
|
||||
/doc/virtualenv
|
||||
virtualenv
|
||||
/Testing
|
||||
/install_manifest.txt
|
||||
*~
|
||||
*.a
|
||||
*.so*
|
||||
*.zip
|
||||
/*.cmake
|
||||
cmake_install.cmake
|
||||
CPack*.cmake
|
||||
fmt-*.cmake
|
||||
CTestTestfile.cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
language: cpp
|
||||
dist: trusty
|
||||
sudo: required # the doc target uses sudo to install dependencies
|
||||
|
||||
os:
|
||||
@@ -22,12 +23,5 @@ matrix:
|
||||
- os: osx
|
||||
env: BUILD=Doc
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- kubuntu-backports # cmake 2.8.12
|
||||
packages:
|
||||
- cmake
|
||||
|
||||
script:
|
||||
- support/travis-build.py
|
||||
|
||||
@@ -9,12 +9,22 @@ if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(MASTER_PROJECT ON)
|
||||
endif ()
|
||||
|
||||
# Joins arguments and places the results in ${result_var}.
|
||||
function(join result_var)
|
||||
set(result )
|
||||
foreach (arg ${ARGN})
|
||||
set(result "${result}${arg}")
|
||||
endforeach ()
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Set the default CMAKE_BUILD_TYPE to Release.
|
||||
# This should be done before the project command since the latter can set
|
||||
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING
|
||||
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
join(doc "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING ${doc})
|
||||
endif ()
|
||||
|
||||
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||
@@ -28,13 +38,17 @@ option(FMT_USE_CPP11 "Enable the addition of C++11 compiler flags." ON)
|
||||
project(FMT)
|
||||
|
||||
# Starting with cmake 3.0 VERSION is part of the project command.
|
||||
set(FMT_VERSION 3.0.0)
|
||||
if (NOT FMT_VERSION MATCHES "^([0-9]+).([0-9]+).([0-9]+)$")
|
||||
message(FATAL_ERROR "Invalid version format ${FMT_VERSION}.")
|
||||
file(READ fmt/format.h format_h)
|
||||
if (NOT format_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
|
||||
message(FATAL_ERROR "Cannot get FMT_VERSION from format.h.")
|
||||
endif ()
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
# Use math to skip leading zeros if any.
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
|
||||
${CPACK_PACKAGE_VERSION_PATCH})
|
||||
message(STATUS "Version: ${FMT_VERSION}")
|
||||
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
@@ -93,7 +107,7 @@ if (MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
string(REPLACE "*" ".*" line "${line}")
|
||||
set(ignored_files ${ignored_files} "${line}$" "${line}/")
|
||||
endforeach ()
|
||||
set(ignored_files ${ignored_files} ${PROJECT_BINARY_DIR}
|
||||
set(ignored_files ${ignored_files}
|
||||
/.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)
|
||||
|
||||
set(CPACK_SOURCE_GENERATOR ZIP)
|
||||
|
||||
11
CONTRIBUTING.rst
Normal file
11
CONTRIBUTING.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
Contributing to fmt
|
||||
===================
|
||||
|
||||
All C++ code must adhere to `Google C++ Style Guide
|
||||
<https://google.github.io/styleguide/cppguide.html>`_ with the following
|
||||
exceptions:
|
||||
|
||||
* Exceptions are permitted
|
||||
* snake_case should be used instead of UpperCamelCase for function names
|
||||
|
||||
Thanks for contributing!
|
||||
110
ChangeLog.rst
110
ChangeLog.rst
@@ -1,3 +1,113 @@
|
||||
4.0.0 - 2017-06-27
|
||||
------------------
|
||||
|
||||
* Removed old compatibility headers ``cppformat/*.h`` and CMake options (`#527 <https://github.com/pull/527>`_). Thanks `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_.
|
||||
|
||||
* Added ``string.h`` containing ``fmt::to_string()`` as alternative to ``std::to_string()`` as well as other string writer functionality (`#326 <https://github.com/fmtlib/fmt/issues/326>`_ and `#441 <https://github.com/fmtlib/fmt/pull/441>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include "fmt/string.h"
|
||||
|
||||
std::string answer = fmt::to_string(42);
|
||||
|
||||
Thanks to `@glebov-andrey (Andrey Glebov) <https://github.com/glebov-andrey>`_.
|
||||
|
||||
* Moved ``fmt::printf()`` to new ``printf.h`` header and allowed ``%s`` as generic specifier (`#453 <https://github.com/fmtlib/fmt/pull/453>`_), made ``%.f`` more conformant to regular ``printf()`` (`#490 <https://github.com/fmtlib/fmt/pull/490>`_), added custom writer support (`#476 <https://github.com/fmtlib/fmt/issues/476>`_) and implemented missing custom argument formatting (`#339 <https://github.com/fmtlib/fmt/pull/339>`_ and `#340 <https://github.com/fmtlib/fmt/pull/340>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include "fmt/printf.h"
|
||||
|
||||
// %s format specifier can be used with any argument type.
|
||||
fmt::printf("%s", 42);
|
||||
|
||||
Thanks `@mojoBrendan <https://github.com/mojoBrendan>`_, `@manylegged (Arthur Danskin) <https://github.com/manylegged>`_ and `@spacemoose (Glen Stark) <https://github.com/spacemoose>`_. See also `#360 <https://github.com/fmtlib/fmt/issues/360>`_, `#335 <https://github.com/fmtlib/fmt/issues/335>`_ and `#331 <https://github.com/fmtlib/fmt/issues/331>`_.
|
||||
|
||||
* Added ``container.h`` containing a ``BasicContainerWriter`` to write to containers like ``std::vector`` (`#450 <https://github.com/fmtlib/fmt/pull/450>`_). Thanks `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_.
|
||||
|
||||
* Added ``fmt::join()`` function that takes a range and formats its elements separated by a given string (`#466 <https://github.com/fmtlib/fmt/pull/466>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
std::vector<double> v = {1.2, 3.4, 5.6};
|
||||
// Prints "(+01.20, +03.40, +05.60)".
|
||||
fmt::print("({:+06.2f})", fmt::join(v.begin(), v.end(), ", "));
|
||||
|
||||
Thanks `@olivier80 <https://github.com/olivier80>`_.
|
||||
|
||||
* Added support for custom formatting specifications to simplify customization of built-in formatting (`#444 <https://github.com/fmtlib/fmt/pull/444>`_). Thanks `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_. See also `#439 <https://github.com/fmtlib/fmt/issues/439>`_.
|
||||
|
||||
* Added ``fmt::format_system_error()`` for error code formatting (`#323 <https://github.com/fmtlib/fmt/issues/323>`_ and `#526 <https://github.com/fmtlib/fmt/pull/526>`_). Thanks `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_.
|
||||
|
||||
* Added thread-safe ``fmt::localtime()`` and ``fmt::gmtime()`` as replacement for the standard version to ``time.h`` (`#396 <https://github.com/fmtlib/fmt/pull/396>`_). Thanks `@codicodi <https://github.com/codicodi>`_.
|
||||
|
||||
* Internal improvements to ``NamedArg`` and ``ArgLists`` (`#389 <https://github.com/fmtlib/fmt/pull/389>`_ and `#390 <https://github.com/fmtlib/fmt/pull/390>`_). Thanks `@chronoxor <https://github.com/chronoxor>`_.
|
||||
|
||||
* Fixed crash due to bug in ``FormatBuf`` (`#493 <https://github.com/fmtlib/fmt/pull/493>`_). Thanks `@effzeh <https://github.com/effzeh>`_. See also `#480 <https://github.com/fmtlib/fmt/issues/480>`_ and `#491 <https://github.com/fmtlib/fmt/issues/491>`_.
|
||||
|
||||
* Fixed handling of wide strings in ``fmt::StringWriter``.
|
||||
|
||||
* Improved compiler error messages (`#357 <https://github.com/fmtlib/fmt/issues/357>`_).
|
||||
|
||||
* Fixed various warnings and issues with various compilers (`#494 <https://github.com/fmtlib/fmt/pull/494>`_, `#499 <https://github.com/fmtlib/fmt/pull/499>`_, `#483 <https://github.com/fmtlib/fmt/pull/483>`_, `#519 <https://github.com/fmtlib/fmt/pull/519>`_, `#485 <https://github.com/fmtlib/fmt/pull/485>`_, `#482 <https://github.com/fmtlib/fmt/pull/482>`_, `#475 <https://github.com/fmtlib/fmt/pull/475>`_, `#473 <https://github.com/fmtlib/fmt/pull/473>`_ and `#414 <https://github.com/fmtlib/fmt/pull/414>`_). Thanks `@chronoxor <https://github.com/chronoxor>`_, `@zhaohuaxishi <https://github.com/zhaohuaxishi>`_, `@pkestene (Pierre Kestener) <https://github.com/pkestene>`_, `@dschmidt (Dominik Schmidt) <https://github.com/dschmidt>`_ and `@0x414c (Alexey Gorishny) <https://github.com/0x414c>`_ .
|
||||
|
||||
* Improved CMake: targets are now namespaced (`#511 <https://github.com/fmtlib/fmt/pull/511>`_ and `#513 <https://github.com/fmtlib/fmt/pull/513>`_), supported header-only ``printf.h`` (`#354 <https://github.com/fmtlib/fmt/pull/354>`_), fixed issue with minimal supported library subset (`#418 <https://github.com/fmtlib/fmt/issues/418>`_, `#419 <https://github.com/fmtlib/fmt/pull/419>`_ and `#420 <https://github.com/fmtlib/fmt/pull/420>`_). Thanks `@bjoernthiel (Bjoern Thiel) <https://github.com/bjoernthiel>`_,
|
||||
`@niosHD (Mario Werner) <https://github.com/niosHD>`_, `@LogicalKnight (Sean LK) <https://github.com/LogicalKnight>`_ and `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
|
||||
|
||||
* Improved documentation. Thanks to `@pwm1234 (Phil) <https://github.com/pwm1234>`_ for `#393 <https://github.com/fmtlib/fmt/pull/393>`_.
|
||||
|
||||
3.0.2 - 2017-06-14
|
||||
------------------
|
||||
|
||||
* Added ``FMT_VERSION`` macro (`#411 <https://github.com/fmtlib/fmt/issues/411>`_).
|
||||
|
||||
* Used ``FMT_NULL`` instead of literal ``0`` (`#409 <https://github.com/fmtlib/fmt/pull/409>`_). Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
|
||||
|
||||
* Added extern templates for ``format_float`` (`#413 <https://github.com/fmtlib/fmt/issues/413>`_).
|
||||
|
||||
* Fixed implicit conversion issue (`#507 <https://github.com/fmtlib/fmt/issues/507>`_).
|
||||
|
||||
* Fixed signbit detection (`#423 <https://github.com/fmtlib/fmt/issues/423>`_).
|
||||
|
||||
* Fixed naming collision (`#425 <https://github.com/fmtlib/fmt/issues/425>`_).
|
||||
|
||||
* Fixed missing intrinsic for C++/CLI (`#457 <https://github.com/fmtlib/fmt/pull/457>`_). Thanks `@calumr (Calum Robinson) <https://github.com/calumr>`_
|
||||
|
||||
* Fixed Android detection (`#458 <https://github.com/fmtlib/fmt/pull/458>`_). Thanks `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_.
|
||||
|
||||
* Use lean ``windows.h`` if not in header-only mode (`#503 <https://github.com/fmtlib/fmt/pull/503>`_). Thanks `@Quentin01 (Quentin Buathier) <https://github.com/Quentin01>`_.
|
||||
|
||||
* Fixed issue with CMake exporting C++11 flag (`#445 <https://github.com/fmtlib/fmt/pull/455>`_). Thanks `@EricWF (Eric) <https://github.com/EricWF>`_.
|
||||
|
||||
* Fixed issue with nvcc and MSVC compiler bug and MinGW (`#505 <https://github.com/fmtlib/fmt/issues/505>`_).
|
||||
|
||||
* Fixed DLL issues (`#469 <https://github.com/fmtlib/fmt/pull/469>`_ and `#502 <https://github.com/fmtlib/fmt/pull/502>`_). Thanks `@richardeakin (Richard Eakin) <https://github.com/richardeakin>`_ and `@AndreasSchoenle (Andreas Schönle) <https://github.com/AndreasSchoenle>`_.
|
||||
|
||||
* Fixed test compilation under FreeBSD (`#433 <https://github.com/fmtlib/fmt/issues/433>`_).
|
||||
|
||||
* Fixed various warnings (`#403 <https://github.com/fmtlib/fmt/pull/403>`_, `#410 <https://github.com/fmtlib/fmt/pull/410>`_ and `#510 <https://github.com/fmtlib/fmt/pull/510>`_). Thanks `@Lecetem <https://github.com/Lectem>`_, `@chenhayat (Chen Hayat) <https://github.com/chenhayat>`_ and `@trozen <https://github.com/trozen>`_.
|
||||
|
||||
* Removed redundant include (`#479 <https://github.com/fmtlib/fmt/issues/479>`_).
|
||||
|
||||
* Fixed documentation issues.
|
||||
|
||||
3.0.1 - 2016-11-01
|
||||
------------------
|
||||
* Fixed handling of thousands seperator (`#353 <https://github.com/fmtlib/fmt/issues/353>`_)
|
||||
|
||||
* Fixed handling of ``unsigned char`` strings (`#373 <https://github.com/fmtlib/fmt/issues/373>`_)
|
||||
|
||||
* Corrected buffer growth when formatting time (`#367 <https://github.com/fmtlib/fmt/issues/367>`_)
|
||||
|
||||
* Removed warnings under MSVC and clang (`#318 <https://github.com/fmtlib/fmt/issues/318>`_, `#250 <https://github.com/fmtlib/fmt/issues/250>`_, also merged `#385 <https://github.com/fmtlib/fmt/pull/385>`_ and `#361 <https://github.com/fmtlib/fmt/pull/361>`_). Thanks `@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_ and `@nmoehrle (Nils Moehrle) <https://github.com/nmoehrle>`_.
|
||||
|
||||
* Fixed compilation issues under Android (`#327 <https://github.com/fmtlib/fmt/pull/327>`_, `#345 <https://github.com/fmtlib/fmt/issues/345>`_ and `#381 <https://github.com/fmtlib/fmt/pull/381>`_), FreeBSD (`#358 <https://github.com/fmtlib/fmt/pull/358>`_), Cygwin (`#388 <https://github.com/fmtlib/fmt/issues/388>`_), MinGW (`#355 <https://github.com/fmtlib/fmt/issues/355>`_) as well as other issues (`#350 <https://github.com/fmtlib/fmt/issues/350>`_, `#366 <https://github.com/fmtlib/fmt/issues/355>`_, `#348 <https://github.com/fmtlib/fmt/pull/348>`_, `#402 <https://github.com/fmtlib/fmt/pull/402>`_, `#405 <https://github.com/fmtlib/fmt/pull/405>`_). Thanks to `@dpantele (Dmitry) <https://github.com/dpantele>`_, `@hghwng (Hugh Wang) <https://github.com/hghwng>`_, `@arvedarved (Tilman Keskinöz) <https://github.com/arvedarved>`_, `@LogicalKnight (Sean) <https://github.com/LogicalKnight>`_ and `@JanHellwig (Jan Hellwig) <https://github.com/janhellwig>`_.
|
||||
|
||||
* Fixed some documentation issues and extended specification (`#320 <https://github.com/fmtlib/fmt/issues/320>`_, `#333 <https://github.com/fmtlib/fmt/pull/333>`_, `#347 <https://github.com/fmtlib/fmt/issues/347>`_, `#362 <https://github.com/fmtlib/fmt/pull/362>`_). Thanks to `@smellman (Taro Matsuzawa aka. btm) <https://github.com/smellman>`_.
|
||||
|
||||
3.0.0 - 2016-05-07
|
||||
------------------
|
||||
|
||||
|
||||
33
README.rst
33
README.rst
@@ -20,9 +20,11 @@ alternative to IOStreams.
|
||||
Features
|
||||
--------
|
||||
|
||||
* Two APIs: faster concatenation-based write API and slower (but still
|
||||
very fast) replacement-based format API with positional arguments for
|
||||
localization.
|
||||
* Two APIs: faster concatenation-based `write API
|
||||
<http://fmtlib.net/latest/api.html#write-api>`_ and slower,
|
||||
but still very fast, replacement-based `format API
|
||||
<http://fmtlib.net/latest/api.html#format-api>`_ with positional arguments
|
||||
for localization.
|
||||
* Write API similar to the one used by IOStreams but stateless allowing
|
||||
faster implementation.
|
||||
* Format API with `format string syntax
|
||||
@@ -35,10 +37,10 @@ Features
|
||||
* Support for user-defined types.
|
||||
* High speed: performance of the format API is close to that of
|
||||
glibc's `printf <http://en.cppreference.com/w/cpp/io/c/fprintf>`_
|
||||
and better than performance of IOStreams. See `Speed tests`_ and
|
||||
and better than the performance of IOStreams. See `Speed tests`_ and
|
||||
`Fast integer to string conversion in C++
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
* Small code size both in terms of source code (format consists of a single
|
||||
* Small code size both in terms of source code (the core library consists of a single
|
||||
header file and a single source file) and compiled code.
|
||||
See `Compile time and code bloat`_.
|
||||
* Reliability: the library has an extensive set of `unit tests
|
||||
@@ -89,6 +91,8 @@ An object of any user-defined type for which there is an overloaded
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include "fmt/ostream.h"
|
||||
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
@@ -132,6 +136,12 @@ Projects using this library
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
An open-source library for mathematical programming
|
||||
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater vehicle
|
||||
|
||||
* `Drake <http://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)
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
@@ -139,6 +149,8 @@ Projects using this library
|
||||
|
||||
* `Keypirinha <http://keypirinha.com/>`_: A semantic launcher for Windows
|
||||
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
|
||||
|
||||
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to generate randomized datasets
|
||||
@@ -158,6 +170,12 @@ Projects using this library
|
||||
* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `Scylla <http://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store that can handle
|
||||
1 million transactions per second on a single server
|
||||
|
||||
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++ framework for
|
||||
high-performance server applications on modern hardware
|
||||
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
|
||||
|
||||
* `Stellar <https://www.stellar.org/>`_: Financial platform
|
||||
@@ -390,6 +408,11 @@ It only applies if you distribute the documentation of fmt.
|
||||
Acknowledgments
|
||||
---------------
|
||||
|
||||
The fmt library is maintained by Victor Zverovich (`vitaut <https://github.com/vitaut>`_)
|
||||
and Jonathan Müller (`foonathan <https://github.com/foonathan>`_) with contributions from many
|
||||
other people. See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and `Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names. Let us know if your contribution
|
||||
is not listed or mentioned incorrectly and we'll make it right.
|
||||
|
||||
The benchmark section of this readme file and the performance tests are taken
|
||||
from the excellent `tinyformat <https://github.com/c42f/tinyformat>`_ library
|
||||
written by Chris Foster. Boost Format library is acknowledged transitively
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#include "../fmt/format.h"
|
||||
#warning Including cppformat/format.h is deprecated. Include fmt/format.h instead.
|
||||
@@ -1,2 +0,0 @@
|
||||
#include "../fmt/posix.h"
|
||||
#warning Including cppformat/posix.h is deprecated. Include fmt/posix.h instead.
|
||||
@@ -5,6 +5,8 @@ if (NOT DOXYGEN)
|
||||
endif ()
|
||||
|
||||
add_custom_target(doc
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION})
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION}
|
||||
SOURCES build.py conf.py _templates/layout.html)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ DESTINATION share/doc/fmt)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
||||
DESTINATION share/doc/fmt OPTIONAL)
|
||||
|
||||
51
doc/_templates/layout.html
vendored
51
doc/_templates/layout.html
vendored
@@ -8,8 +8,9 @@
|
||||
{# Google Analytics #}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();
|
||||
a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;
|
||||
a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-20116650-4', 'fmtlib.net');
|
||||
ga('send', 'pageview');
|
||||
@@ -17,9 +18,11 @@
|
||||
{% endblock %}
|
||||
|
||||
{%- macro searchform(classes, button) %}
|
||||
<form class="{{classes}}" role="search" action="{{ pathto('search') }}" method="get">
|
||||
<form class="{{classes}}" role="search" action="{{ pathto('search') }}"
|
||||
method="get">
|
||||
<div class="form-group">
|
||||
<input type="text" name="q" class="form-control" {{ 'placeholder="Search"' if not button }} >
|
||||
<input type="text" name="q" class="form-control"
|
||||
{{ 'placeholder="Search"' if not button }} >
|
||||
</div>
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
@@ -36,7 +39,8 @@
|
||||
<div class="navbar-content">
|
||||
{# Brand and toggle get grouped for better mobile display #}
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<button type="button" class="navbar-toggle collapsed"
|
||||
data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
@@ -49,18 +53,19 @@
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
{# TODO: update versions automatically #}
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
|
||||
aria-expanded="false">{{ version }} <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
|
||||
role="button" aria-expanded="false">{{ version }}
|
||||
<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="http://fmtlib.net/2.0.0/">2.0.0</a></li>
|
||||
<li><a href="http://fmtlib.net/1.1.0/">1.1.0</a></li>
|
||||
<li><a href="http://fmtlib.net/1.0.0/">1.0.0</a></li>
|
||||
{% for v in versions.split(',') %}
|
||||
<li><a href="http://fmtlib.net/{{v}}">{{v}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% for name in ['Contents', 'Usage', 'API', 'Syntax'] %}
|
||||
{% if pagename == name.lower() %}
|
||||
<li class="active"><a href="{{name.lower()}}.html">{{name}} <span class="sr-only">(current)</span></a></li>
|
||||
<li class="active"><a href="{{name.lower()}}.html">{{name}}
|
||||
<span class="sr-only">(current)</span></a></li>
|
||||
{%else%}
|
||||
<li><a href="{{name.lower()}}.html">{{name}}</a></li>
|
||||
{%endif%}
|
||||
@@ -75,20 +80,25 @@
|
||||
</div> {# /.tb-container #}
|
||||
</nav>
|
||||
{% if pagename == "index" %}
|
||||
{% set download_url = 'https://github.com/fmtlib/fmt/releases/download' %}
|
||||
<div class="jumbotron">
|
||||
<div class="tb-container">
|
||||
<h1>{fmt}</h1>
|
||||
<p class="lead">Small, safe and fast formatting library</p>
|
||||
<div class="btn-group" role="group">
|
||||
{% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||
<a class="btn btn-success"
|
||||
href="https://github.com/fmtlib/fmt/releases/download/2.0.0/cppformat-2.0.0.zip">
|
||||
<span class="glyphicon glyphicon-download"></span> Download
|
||||
href="{{download_url}}/{{version}}/{{name}}-{{version}}.zip">
|
||||
<span class="glyphicon glyphicon-download"></span> Download
|
||||
</a>
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<button type="button" class="btn btn-success dropdown-toggle"
|
||||
data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="https://github.com/fmtlib/fmt/releases/download/2.0.0/cppformat-2.0.0.zip">Version 2.0.0</a></li>
|
||||
<li><a href="https://github.com/fmtlib/fmt/releases/download/1.1.0/cppformat-1.1.0.zip">Version 1.1.0</a></li>
|
||||
<li><a href="https://github.com/fmtlib/fmt/releases/download/1.0.0/cppformat-1.0.0.zip">Version 1.0.0</a></li>
|
||||
{% for v in versions.split(',') %}
|
||||
{% set name = 'fmt' if v.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||
<li><a href="{{download_url}}/{{v}}/{{name}}-{{v}}.zip">Version {{v}}
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,14 +115,15 @@
|
||||
{% block content %}
|
||||
<div class="tb-container">
|
||||
<div class="row">
|
||||
{# TODO: integrate sidebar
|
||||
{# Sidebar is currently disabled.
|
||||
<div class="bs-sidebar">
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}"
|
||||
alt="Logo"/>
|
||||
</a></p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
90
doc/api.rst
90
doc/api.rst
@@ -11,8 +11,8 @@ namespace is usually omitted in examples.
|
||||
Format API
|
||||
==========
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>` similar
|
||||
to the one used by Python's `str.format
|
||||
The following functions defined in ``fmt/format.h`` use :ref:`format string
|
||||
syntax <syntax>` similar to the one used by Python's `str.format
|
||||
<http://docs.python.org/3/library/stdtypes.html#str.format>`_ function.
|
||||
They take *format_str* and *args* as arguments.
|
||||
|
||||
@@ -22,6 +22,11 @@ arguments in the resulting string.
|
||||
|
||||
*args* is an argument list representing arbitrary arguments.
|
||||
|
||||
The `performance of the format API
|
||||
<https://github.com/fmtlib/fmt/blob/master/README.rst#speed-tests>`_ is close
|
||||
to that of glibc's ``printf`` and better than the performance of IOStreams.
|
||||
For even better speed use the `write API`_.
|
||||
|
||||
.. _format:
|
||||
|
||||
.. doxygenfunction:: format(CStringRef, ArgList)
|
||||
@@ -40,8 +45,9 @@ arguments in the resulting string.
|
||||
Date and time formatting
|
||||
------------------------
|
||||
|
||||
The library supports `strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like
|
||||
date and time formatting::
|
||||
The library supports `strftime
|
||||
<http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like date and time
|
||||
formatting::
|
||||
|
||||
#include "fmt/time.h"
|
||||
|
||||
@@ -52,6 +58,36 @@ date and time formatting::
|
||||
The format string syntax is described in the documentation of
|
||||
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
|
||||
|
||||
Formatting user-defined types
|
||||
-----------------------------
|
||||
|
||||
A custom ``format_arg`` function may be implemented and used to format any
|
||||
user-defined type. That is how date and time formatting described in the
|
||||
previous section is implemented in :file:`fmt/time.h`. The following example
|
||||
shows how to implement custom formatting for a user-defined structure.
|
||||
|
||||
::
|
||||
|
||||
struct MyStruct { double a, b; };
|
||||
|
||||
void format_arg(fmt::BasicFormatter<char> &f,
|
||||
const char *&format_str, const MyStruct &s) {
|
||||
f.writer().write("[MyStruct: a={:.1f}, b={:.2f}]", s.a, s.b);
|
||||
}
|
||||
|
||||
MyStruct m = { 1, 2 };
|
||||
std::string s = fmt::format("m={}", n);
|
||||
// s == "m=[MyStruct: a=1.0, b=2.00]"
|
||||
|
||||
Note in the example above the ``format_arg`` function ignores the contents of
|
||||
``format_str`` so the type will always be formatted as specified. See
|
||||
``format_arg`` in :file:`fmt/time.h` for an advanced example of how to use
|
||||
the ``format_str`` argument to customize the formatted output.
|
||||
|
||||
This section shows how to define a custom format function for a user-defined
|
||||
type. The next section describes how to get ``fmt`` to use a conventional stream
|
||||
output ``operator<<`` when one is defined for a user-defined type.
|
||||
|
||||
``std::ostream`` support
|
||||
------------------------
|
||||
|
||||
@@ -63,7 +99,7 @@ formatting of user-defined types that have overloaded ``operator<<``::
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
|
||||
Date(int year, int month, int day): year_(year), month_(month), day_(day) {}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Date &d) {
|
||||
return os << d.year_ << '-' << d.month_ << '-' << d.day_;
|
||||
@@ -75,8 +111,6 @@ formatting of user-defined types that have overloaded ``operator<<``::
|
||||
|
||||
.. doxygenfunction:: print(std::ostream&, CStringRef, ArgList)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::ostream&, CStringRef, ArgList)
|
||||
|
||||
Argument formatters
|
||||
-------------------
|
||||
|
||||
@@ -86,7 +120,7 @@ custom argument formatter class::
|
||||
// A custom argument formatter that formats negative integers as unsigned
|
||||
// with the ``x`` format specifier.
|
||||
class CustomArgFormatter :
|
||||
public fmt::BasicArgFormatter<CustomArgFormatter, char> {
|
||||
public fmt::BasicArgFormatter<CustomArgFormatter, char> {
|
||||
public:
|
||||
CustomArgFormatter(fmt::BasicFormatter<char, CustomArgFormatter> &f,
|
||||
fmt::FormatSpec &s, const char *fmt)
|
||||
@@ -120,22 +154,43 @@ custom argument formatter class::
|
||||
.. doxygenclass:: fmt::ArgFormatter
|
||||
:members:
|
||||
|
||||
Printf formatting functions
|
||||
---------------------------
|
||||
Printf formatting
|
||||
-----------------
|
||||
|
||||
The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
|
||||
The following functions use `printf format string syntax
|
||||
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||
a POSIX extension for positional arguments.
|
||||
the POSIX extension for positional arguments. Unlike their standard
|
||||
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
|
||||
argument type doesn't match its format specification.
|
||||
|
||||
.. doxygenfunction:: printf(CStringRef, ArgList)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::FILE *, CStringRef, ArgList)
|
||||
|
||||
.. doxygenfunction:: fprintf(std::ostream&, CStringRef, ArgList)
|
||||
|
||||
.. doxygenfunction:: sprintf(CStringRef, ArgList)
|
||||
|
||||
.. doxygenclass:: fmt::PrintfFormatter
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicPrintfArgFormatter
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::PrintfArgFormatter
|
||||
:members:
|
||||
|
||||
Write API
|
||||
=========
|
||||
|
||||
The write API provides classes for writing formatted data into character
|
||||
streams. It is usually faster than the `format API`_ but, as IOStreams,
|
||||
may result in larger compiled code size. The main writer class is
|
||||
`~fmt::BasicMemoryWriter` which stores its output in a memory buffer and
|
||||
provides direct access to it. It is possible to create custom writers that
|
||||
store output elsewhere by subclassing `~fmt::BasicWriter`.
|
||||
|
||||
.. doxygenclass:: fmt::BasicWriter
|
||||
:members:
|
||||
|
||||
@@ -145,6 +200,12 @@ Write API
|
||||
.. doxygenclass:: fmt::BasicArrayWriter
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicStringWriter
|
||||
:members:
|
||||
|
||||
.. doxygenclass:: fmt::BasicContainerWriter
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: bin(int)
|
||||
|
||||
.. doxygenfunction:: oct(int)
|
||||
@@ -169,6 +230,8 @@ Utilities
|
||||
.. doxygenclass:: fmt::ArgList
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: fmt::to_string(const T&)
|
||||
|
||||
.. doxygenclass:: fmt::BasicStringRef
|
||||
:members:
|
||||
|
||||
@@ -185,6 +248,8 @@ System errors
|
||||
.. doxygenclass:: fmt::SystemError
|
||||
:members:
|
||||
|
||||
.. doxygenfunction:: fmt::format_system_error
|
||||
|
||||
.. doxygenclass:: fmt::WindowsError
|
||||
:members:
|
||||
|
||||
@@ -202,7 +267,8 @@ A custom allocator class can be specified as a template argument to
|
||||
It is also possible to write a formatting function that uses a custom
|
||||
allocator::
|
||||
|
||||
typedef std::basic_string<char, std::char_traits<char>, CustomAllocator> CustomString;
|
||||
typedef std::basic_string<char, std::char_traits<char>, CustomAllocator>
|
||||
CustomString;
|
||||
|
||||
CustomString format(CustomAllocator alloc, fmt::CStringRef format_str,
|
||||
fmt::ArgList args) {
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
HAS_SOURCE: {{ has_source|lower }},
|
||||
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
|
||||
};
|
||||
</script>
|
||||
{%- for scriptfile in script_files %}
|
||||
|
||||
74
doc/build.py
74
doc/build.py
@@ -8,30 +8,35 @@ from distutils.version import LooseVersion
|
||||
|
||||
def pip_install(package, commit=None, **kwargs):
|
||||
"Install package using pip."
|
||||
min_version = kwargs.get('min_version')
|
||||
if min_version:
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
try:
|
||||
installed_version = get_distribution(os.path.basename(package)).version
|
||||
if LooseVersion(installed_version) >= min_version:
|
||||
print('{} {} already installed'.format(package, min_version))
|
||||
return
|
||||
except DistributionNotFound:
|
||||
pass
|
||||
if commit:
|
||||
check_version = kwargs.get('check_version', '')
|
||||
#output = check_output(['pip', 'show', package.split('/')[1]])
|
||||
#if check_version in output:
|
||||
# print('{} already installed'.format(package))
|
||||
# return
|
||||
package = 'git+git://github.com/{0}.git@{1}'.format(package, commit)
|
||||
print('Installing {}'.format(package))
|
||||
check_call(['pip', 'install', '--upgrade', package])
|
||||
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
|
||||
print('Installing {0}'.format(package))
|
||||
check_call(['pip', 'install', package])
|
||||
|
||||
def build_docs(version='dev'):
|
||||
def create_build_env(dirname='virtualenv'):
|
||||
# Create virtualenv.
|
||||
doc_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
virtualenv_dir = 'virtualenv'
|
||||
check_call(['virtualenv', virtualenv_dir])
|
||||
if not os.path.exists(dirname):
|
||||
check_call(['virtualenv', dirname])
|
||||
import sysconfig
|
||||
scripts_dir = os.path.basename(sysconfig.get_path('scripts'))
|
||||
activate_this_file = os.path.join(virtualenv_dir, scripts_dir,
|
||||
'activate_this.py')
|
||||
activate_this_file = os.path.join(dirname, scripts_dir, 'activate_this.py')
|
||||
with open(activate_this_file) as f:
|
||||
exec(f.read(), dict(__file__=activate_this_file))
|
||||
# Import get_distribution after activating virtualenv to get info about
|
||||
# the correct packages.
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
# Upgrade pip because installation of sphinx with pip 1.1 available on Travis
|
||||
# is broken (see #207) and it doesn't support the show command.
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
pip_version = get_distribution('pip').version
|
||||
if LooseVersion(pip_version) < LooseVersion('1.5.4'):
|
||||
print("Updating pip")
|
||||
@@ -46,27 +51,35 @@ def build_docs(version='dev'):
|
||||
except DistributionNotFound:
|
||||
pass
|
||||
# Install Sphinx and Breathe.
|
||||
pip_install('fmtlib/sphinx',
|
||||
'12dde8afdb0a7bb5576e2656692c3478c69d8cc3',
|
||||
check_version='1.4a0.dev-20151013')
|
||||
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee',
|
||||
min_version='1.4.1.dev20160531')
|
||||
pip_install('michaeljones/breathe',
|
||||
'1c9d7f80378a92cffa755084823a78bb38ee4acc')
|
||||
'6b1c5bb7a1866f15fc328b8716258354b10c1daa',
|
||||
min_version='4.2.0')
|
||||
|
||||
def build_docs(version='dev', **kwargs):
|
||||
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
|
||||
work_dir = kwargs.get('work_dir', '.')
|
||||
include_dir = kwargs.get('include_dir',
|
||||
os.path.join(os.path.dirname(doc_dir), 'fmt'))
|
||||
# Build docs.
|
||||
cmd = ['doxygen', '-']
|
||||
p = Popen(cmd, stdin=PIPE)
|
||||
doxyxml_dir = os.path.join(work_dir, 'doxyxml')
|
||||
p.communicate(input=r'''
|
||||
PROJECT_NAME = fmt
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_MAN = NO
|
||||
GENERATE_RTF = NO
|
||||
CASE_SENSE_NAMES = NO
|
||||
INPUT = {0}/format.h {0}/ostream.h
|
||||
INPUT = {0}/container.h {0}/format.h {0}/ostream.h \
|
||||
{0}/printf.h {0}/string.h
|
||||
QUIET = YES
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
AUTOLINK_SUPPORT = NO
|
||||
GENERATE_HTML = NO
|
||||
GENERATE_XML = YES
|
||||
XML_OUTPUT = doxyxml
|
||||
XML_OUTPUT = {1}
|
||||
ALIASES = "rst=\verbatim embed:rst"
|
||||
ALIASES += "endrst=\endverbatim"
|
||||
MACRO_EXPANSION = YES
|
||||
@@ -76,24 +89,29 @@ def build_docs(version='dev'):
|
||||
FMT_USE_USER_DEFINED_LITERALS=1 \
|
||||
FMT_API=
|
||||
EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str
|
||||
'''.format(os.path.join(os.path.dirname(doc_dir), 'fmt')).encode('UTF-8'))
|
||||
'''.format(include_dir, doxyxml_dir).encode('UTF-8'))
|
||||
if p.returncode != 0:
|
||||
raise CalledProcessError(p.returncode, cmd)
|
||||
html_dir = os.path.join(work_dir, 'html')
|
||||
versions = ['3.0.0', '2.0.0', '1.1.0']
|
||||
check_call(['sphinx-build',
|
||||
'-Dbreathe_projects.format=' + os.path.join(os.getcwd(), 'doxyxml'),
|
||||
'-Dversion=' + version, '-Drelease=' + version, '-Aversion=' + version,
|
||||
'-b', 'html', doc_dir, 'html'])
|
||||
'-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir),
|
||||
'-Dversion=' + version, '-Drelease=' + version,
|
||||
'-Aversion=' + version, '-Aversions=' + ','.join(versions),
|
||||
'-b', 'html', doc_dir, html_dir])
|
||||
try:
|
||||
check_call(['lessc', '--clean-css',
|
||||
'--include-path=' + os.path.join(doc_dir, 'bootstrap'),
|
||||
os.path.join(doc_dir, 'fmt.less'),
|
||||
'html/_static/fmt.css'])
|
||||
os.path.join(html_dir, '_static', 'fmt.css')])
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
print('lessc not found; make sure that Less (http://lesscss.org/) is installed')
|
||||
print('lessc not found; make sure that Less (http://lesscss.org/) ' +
|
||||
'is installed')
|
||||
sys.exit(1)
|
||||
return 'html'
|
||||
return html_dir
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_build_env()
|
||||
build_docs(sys.argv[1])
|
||||
|
||||
@@ -228,8 +228,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'format', u'format Documentation',
|
||||
[u'Victor Zverovich'], 1)
|
||||
('index', 'fmt', u'fmt documentation', [u'Victor Zverovich'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@@ -242,8 +241,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'format', u'format Documentation',
|
||||
u'Victor Zverovich', 'format', 'One line description of project.',
|
||||
('index', 'fmt', u'fmt documentation',
|
||||
u'Victor Zverovich', 'fmt', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ alternative to C++ IOStreams.
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">What users say:</div>
|
||||
<div class="panel-body">
|
||||
Thanks for creating this library. It’s been a hole in C++ for a long time.
|
||||
I’ve used both boost::format and loki::SPrintf, and neither felt like the
|
||||
right answer. This does.
|
||||
Thanks for creating this library. It’s been a hole in C++ for a long
|
||||
time. I’ve used both boost::format and loki::SPrintf, and neither felt
|
||||
like the right answer. This does.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,8 +24,8 @@ Format API
|
||||
The replacement-based Format API provides a safe alternative to ``printf``,
|
||||
``sprintf`` and friends with comparable or `better performance
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
The `format string syntax <doc/latest/index.html#format-string-syntax>`_ is similar
|
||||
to the one used by `str.format <http://docs.python.org/2/library/stdtypes.html#str.format>`_
|
||||
The `format string syntax <syntax.html>`_ is similar to the one used by
|
||||
`str.format <http://docs.python.org/2/library/stdtypes.html#str.format>`_
|
||||
in Python:
|
||||
|
||||
.. code:: c++
|
||||
@@ -98,8 +98,8 @@ literal operators, they must be made visible with the directive
|
||||
Write API
|
||||
---------
|
||||
|
||||
The concatenation-based Write API (experimental) provides a
|
||||
`fast <http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
|
||||
The concatenation-based Write API (experimental) provides a `fast
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
|
||||
stateless alternative to IOStreams:
|
||||
|
||||
.. code:: c++
|
||||
@@ -112,8 +112,9 @@ stateless alternative to IOStreams:
|
||||
Safety
|
||||
------
|
||||
|
||||
The library is fully type safe, automatic memory management prevents buffer overflow,
|
||||
errors in format strings are reported using exceptions. For example, the code
|
||||
The library is fully type safe, automatic memory management prevents buffer
|
||||
overflow, errors in format strings are reported using exceptions. For example,
|
||||
the code
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@@ -138,19 +139,21 @@ formatted into a narrow string. You can use a wide format string instead:
|
||||
fmt::format(L"Cyrillic letter {}", L'\x42e');
|
||||
|
||||
For comparison, writing a wide character to ``std::ostream`` results in
|
||||
its numeric value being written to the stream (i.e. 1070 instead of letter 'ю' which
|
||||
is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is needed.
|
||||
its numeric value being written to the stream (i.e. 1070 instead of letter 'ю'
|
||||
which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is
|
||||
needed.
|
||||
|
||||
.. _portability:
|
||||
|
||||
Portability
|
||||
-----------
|
||||
|
||||
The library is highly portable. Here is an incomplete list of operating systems and
|
||||
compilers where it has been tested and known to work:
|
||||
The library is highly portable. Here is an incomplete list of operating systems
|
||||
and compilers where it has been tested and known to work:
|
||||
|
||||
* 64-bit (amd64) GNU/Linux with GCC 4.4.3, `4.6.3 <https://travis-ci.org/fmtlib/fmt>`_,
|
||||
4.7.2, 4.8.1 and Intel C++ Compiler (ICC) 14.0.2
|
||||
* 64-bit (amd64) GNU/Linux with GCC 4.4.3,
|
||||
`4.6.3 <https://travis-ci.org/fmtlib/fmt>`_, 4.7.2, 4.8.1, and Intel C++
|
||||
Compiler (ICC) 14.0.2
|
||||
|
||||
* 32-bit (i386) GNU/Linux with GCC 4.4.3, 4.6.3
|
||||
|
||||
@@ -161,21 +164,21 @@ compilers where it has been tested and known to work:
|
||||
|
||||
* 32-bit Windows with Visual C++ 2010
|
||||
|
||||
Although the library uses C++11 features when available, it also works with older
|
||||
compilers and standard library implementations. The only thing to keep in mind
|
||||
for C++98 portability:
|
||||
Although the library uses C++11 features when available, it also works with
|
||||
older compilers and standard library implementations. The only thing to keep in
|
||||
mind for C++98 portability:
|
||||
|
||||
* Variadic templates: minimum GCC 4.4, Clang 2.9 or VS2013. This feature allows
|
||||
the Format API to accept an unlimited number of arguments. With older compilers
|
||||
the maximum is 15.
|
||||
the Format API to accept an unlimited number of arguments. With older
|
||||
compilers the maximum is 15.
|
||||
|
||||
* User-defined literals: minimum GCC 4.7, Clang 3.1 or VS2015. The suffixes
|
||||
``_format`` and ``_a`` are functionally equivalent to the functions
|
||||
* User-defined literals: minimum GCC 4.7, Clang 3.1 or VS2015. The suffixes
|
||||
``_format`` and ``_a`` are functionally equivalent to the functions
|
||||
``fmt::format`` and ``fmt::arg``.
|
||||
|
||||
The output of all formatting functions is consistent across platforms. In particular,
|
||||
formatting a floating-point infinity always gives ``inf`` while the output
|
||||
of ``printf`` is platform-dependent in this case. For example,
|
||||
The output of all formatting functions is consistent across platforms. In
|
||||
particular, formatting a floating-point infinity always gives ``inf`` while the
|
||||
output of ``printf`` is platform-dependent in this case. For example,
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -188,10 +191,10 @@ always prints ``inf``.
|
||||
Ease of Use
|
||||
-----------
|
||||
|
||||
fmt has a small self-contained code base consisting of a single header file
|
||||
and a single source file and no external dependencies. A permissive BSD `license
|
||||
<https://github.com/fmtlib/fmt#license>`_ allows using the library both
|
||||
in open-source and commercial projects.
|
||||
fmt has a small self-contained code base with the core library consisting of
|
||||
a single header file and a single source file and no external dependencies.
|
||||
A permissive BSD `license <https://github.com/fmtlib/fmt#license>`_ allows
|
||||
using the library both in open-source and commercial projects.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
|
||||
@@ -49,12 +49,10 @@ mini-language" or interpretation of the *format_spec*.
|
||||
Most built-in types support a common formatting mini-language, which is
|
||||
described in the next section.
|
||||
|
||||
A *format_spec* field can also include nested replacement fields within it.
|
||||
These nested replacement fields can contain only an argument index;
|
||||
format specifications are not allowed. Formatting is performed as if the
|
||||
replacement fields within the format_spec are substituted before the
|
||||
*format_spec* string is interpreted. This allows the formatting of a value
|
||||
to be dynamically specified.
|
||||
A *format_spec* field can also include nested replacement fields in certain
|
||||
positions within it. These nested replacement fields can contain only an
|
||||
argument id; format specifications are not allowed. This allows the
|
||||
formatting of a value to be dynamically specified.
|
||||
|
||||
See the :ref:`formatexamples` section for some examples.
|
||||
|
||||
@@ -80,8 +78,8 @@ The general form of a *standard format specifier* is:
|
||||
sign: "+" | "-" | " "
|
||||
width: `integer` | "{" `arg_id` "}"
|
||||
precision: `integer` | "{" `arg_id` "}"
|
||||
type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X"
|
||||
|
||||
The *fill* character can be any character other than '{' or '}'. The presence
|
||||
of a fill character is signaled by the character following it, which must be
|
||||
@@ -234,7 +232,7 @@ The available presentation types for floating-point values are:
|
||||
+=========+==========================================================+
|
||||
| ``'a'`` | Hexadecimal floating point format. Prints the number in |
|
||||
| | base 16 with prefix ``"0x"`` and lower-case letters for |
|
||||
| | digits above 9. Uses 'p' to indicate the exponent. |
|
||||
| | digits above 9. Uses ``'p'`` to indicate the exponent. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'A'`` | Same as ``'a'`` except it uses upper-case letters for |
|
||||
| | the prefix, digits above 9 and to indicate the exponent. |
|
||||
|
||||
@@ -54,6 +54,23 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
|
||||
|
||||
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
||||
|
||||
Header-only usage with CMake
|
||||
============================
|
||||
|
||||
In order to add ``fmtlib`` into an existing ``CMakeLists.txt`` file, you can add the ``fmt`` library directory into your main project, which will enable the ``fmt`` library::
|
||||
|
||||
add_subdirectory(fmt)
|
||||
|
||||
If you have a project called ``foo`` that you would like to link against the fmt library in a header-only fashion, you can enable with with::
|
||||
|
||||
target_link_libraries(foo PRIVATE fmt::fmt-header-only)
|
||||
|
||||
And then to ensure that the ``fmt`` library does not always get built, you can modify the call to ``add_subdirectory`` to read ::
|
||||
|
||||
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||
|
||||
This will ensure that the ``fmt`` library is exluded from calls to ``make``, ``make all``, or ``cmake --build .``.
|
||||
|
||||
Building the documentation
|
||||
==========================
|
||||
|
||||
@@ -62,7 +79,11 @@ system:
|
||||
|
||||
* `Python <https://www.python.org/>`_ with pip and virtualenv
|
||||
* `Doxygen <http://www.stack.nl/~dimitri/doxygen/>`_
|
||||
* `Less <http://lesscss.org/>`_ with less-plugin-clean-css
|
||||
* `Less <http://lesscss.org/>`_ with ``less-plugin-clean-css``.
|
||||
Ubuntu doesn't package the ``clean-css`` plugin so you should use ``npm``
|
||||
instead of ``apt`` to install both ``less`` and the plugin::
|
||||
|
||||
sudo npm install -g less less-plugin-clean-css.
|
||||
|
||||
First generate makefiles or project files using CMake as described in
|
||||
the previous section. Then compile the ``doc`` target/project, for example::
|
||||
@@ -87,4 +108,4 @@ Homebrew
|
||||
|
||||
fmt can be installed on OS X using `Homebrew <http://brew.sh/>`_::
|
||||
|
||||
brew install cppformat
|
||||
brew install fmt
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# Define the fmt library, its includes and the needed defines.
|
||||
# format.cc is added to FMT_HEADERS for the header-only configuration.
|
||||
set(FMT_HEADERS format.h format.cc ostream.h ostream.cc time.h)
|
||||
# *.cc are added to FMT_HEADERS for the header-only configuration.
|
||||
set(FMT_HEADERS container.h format.h format.cc ostream.h ostream.cc printf.h
|
||||
printf.cc string.h time.h)
|
||||
if (HAVE_OPEN)
|
||||
set(FMT_HEADERS ${FMT_HEADERS} posix.h)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} posix.cc)
|
||||
endif ()
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} ../ChangeLog.rst)
|
||||
|
||||
option(FMT_CPPFORMAT "Build cppformat library for backward compatibility." OFF)
|
||||
if (FMT_CPPFORMAT)
|
||||
message(WARNING "The cppformat library is deprecated, use fmt instead.")
|
||||
add_library(cppformat ${FMT_SOURCES} ${FMT_HEADERS})
|
||||
endif ()
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} ../README.rst ../ChangeLog.rst)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
|
||||
# Starting with cmake 3.1 the CXX_STANDARD property can be used instead.
|
||||
target_compile_options(fmt PUBLIC ${CPP11_FLAG})
|
||||
# Note: Don't make -std=c++11 public or interface, since it breaks projects
|
||||
# that use C++14.
|
||||
target_compile_options(fmt PRIVATE ${CPP11_FLAG})
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
|
||||
target_include_directories(fmt INTERFACE
|
||||
target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
@@ -40,6 +38,7 @@ endif ()
|
||||
# additionally define a header only library when cmake is new enough
|
||||
if (CMAKE_VERSION VERSION_GREATER 3.1.0 OR CMAKE_VERSION VERSION_EQUAL 3.1.0)
|
||||
add_library(fmt-header-only INTERFACE)
|
||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
|
||||
@@ -74,18 +73,18 @@ if (FMT_INSTALL)
|
||||
${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
|
||||
${project_config}
|
||||
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||
export(TARGETS ${INSTALL_TARGETS} FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
||||
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||
|
||||
# Install version, config and target files.
|
||||
install(
|
||||
FILES ${project_config} ${version_config}
|
||||
DESTINATION ${FMT_CMAKE_DIR})
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR})
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||
NAMESPACE fmt::)
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} DESTINATION ${FMT_LIB_DIR})
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||
DESTINATION ${FMT_LIB_DIR})
|
||||
install(FILES ${FMT_HEADERS} DESTINATION include/fmt)
|
||||
if (FMT_CPPFORMAT)
|
||||
install(TARGETS cppformat DESTINATION ${FMT_LIB_DIR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
82
fmt/container.h
Normal file
82
fmt/container.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Formatting library for C++ - standard container utilities
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#ifndef FMT_CONTAINER_H_
|
||||
#define FMT_CONTAINER_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
namespace fmt {
|
||||
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
\rst
|
||||
A "buffer" that appends data to a standard container (e.g. typically a
|
||||
``std::vector`` or ``std::basic_string``).
|
||||
\endrst
|
||||
*/
|
||||
template <typename Container>
|
||||
class ContainerBuffer : public Buffer<typename Container::value_type> {
|
||||
private:
|
||||
Container& container_;
|
||||
|
||||
protected:
|
||||
virtual void grow(std::size_t size) FMT_OVERRIDE {
|
||||
container_.resize(size);
|
||||
this->ptr_ = &container_[0];
|
||||
this->capacity_ = size;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ContainerBuffer(Container& container) : container_(container) {
|
||||
this->size_ = container_.size();
|
||||
if (this->size_ > 0) {
|
||||
this->ptr_ = &container_[0];
|
||||
this->capacity_ = this->size_;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
\rst
|
||||
This class template provides operations for formatting and appending data
|
||||
to a standard *container* like ``std::vector`` or ``std::basic_string``.
|
||||
|
||||
**Example**::
|
||||
|
||||
void vecformat(std::vector<char>& dest, fmt::BasicCStringRef<char> format,
|
||||
fmt::ArgList args) {
|
||||
fmt::BasicContainerWriter<std::vector<char> > appender(dest);
|
||||
appender.write(format, args);
|
||||
}
|
||||
FMT_VARIADIC(void, vecformat, std::vector<char>&,
|
||||
fmt::BasicCStringRef<char>);
|
||||
\endrst
|
||||
*/
|
||||
template <class Container>
|
||||
class BasicContainerWriter
|
||||
: public BasicWriter<typename Container::value_type> {
|
||||
private:
|
||||
internal::ContainerBuffer<Container> buffer_;
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`fmt::BasicContainerWriter` object.
|
||||
\endrst
|
||||
*/
|
||||
explicit BasicContainerWriter(Container& dest)
|
||||
: BasicWriter<typename Container::value_type>(buffer_), buffer_(dest) {}
|
||||
};
|
||||
|
||||
} // namespace fmt
|
||||
|
||||
#endif // FMT_CONTAINER_H_
|
||||
534
fmt/format.cc
534
fmt/format.cc
@@ -41,6 +41,9 @@
|
||||
#endif
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
|
||||
# include <windows.h>
|
||||
# else
|
||||
@@ -50,8 +53,6 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
using fmt::internal::Arg;
|
||||
|
||||
#if FMT_EXCEPTIONS
|
||||
# define FMT_TRY try
|
||||
# define FMT_CATCH(x) catch (x)
|
||||
@@ -79,6 +80,11 @@ static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) {
|
||||
}
|
||||
|
||||
namespace fmt {
|
||||
|
||||
FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {}
|
||||
FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {}
|
||||
FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {}
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef _MSC_VER
|
||||
@@ -100,27 +106,6 @@ inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||
# define FMT_SWPRINTF swprintf
|
||||
#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned>
|
||||
struct IntChecker {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
unsigned max = INT_MAX;
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IntChecker<true> {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
return value >= INT_MIN && value <= INT_MAX;
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
const char RESET_COLOR[] = "\x1b[0m";
|
||||
|
||||
typedef void (*FormatFunc)(Writer &, int, StringRef);
|
||||
@@ -186,7 +171,8 @@ int safe_strerror(
|
||||
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
|
||||
|
||||
int run() {
|
||||
strerror_r(0, 0, ""); // Suppress a warning about unused strerror_r.
|
||||
// Suppress a warning about unused strerror_r.
|
||||
strerror_r(0, FMT_NULL, "");
|
||||
return handle(strerror_r(error_code_, buffer_, buffer_size_));
|
||||
}
|
||||
};
|
||||
@@ -225,222 +211,19 @@ void report_error(FormatFunc func, int error_code,
|
||||
std::fwrite(full_message.data(), full_message.size(), 1, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
}
|
||||
|
||||
// IsZeroInt::visit(arg) returns true iff arg is a zero integer.
|
||||
class IsZeroInt : public ArgVisitor<IsZeroInt, bool> {
|
||||
public:
|
||||
template <typename T>
|
||||
bool visit_any_int(T value) { return value == 0; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
class WidthHandler : public ArgVisitor<WidthHandler, unsigned> {
|
||||
private:
|
||||
FormatSpec &spec_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler);
|
||||
|
||||
public:
|
||||
explicit WidthHandler(FormatSpec &spec) : spec_(spec) {}
|
||||
|
||||
void report_unhandled_arg() {
|
||||
FMT_THROW(FormatError("width is not integer"));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
unsigned visit_any_int(T value) {
|
||||
typedef typename internal::IntTraits<T>::MainType UnsignedType;
|
||||
UnsignedType width = static_cast<UnsignedType>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
spec_.align_ = ALIGN_LEFT;
|
||||
width = 0 - width;
|
||||
}
|
||||
if (width > INT_MAX)
|
||||
FMT_THROW(FormatError("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
};
|
||||
|
||||
class PrecisionHandler : public ArgVisitor<PrecisionHandler, int> {
|
||||
public:
|
||||
void report_unhandled_arg() {
|
||||
FMT_THROW(FormatError("precision is not integer"));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int visit_any_int(T value) {
|
||||
if (!IntChecker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(FormatError("number is too big"));
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct is_same {
|
||||
enum { value = 0 };
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_same<T, T> {
|
||||
enum { value = 1 };
|
||||
};
|
||||
|
||||
// An argument visitor that converts an integer argument to T for printf,
|
||||
// if T is an integral type. If T is void, the argument is converted to
|
||||
// corresponding signed or unsigned type depending on the type specifier:
|
||||
// 'd' and 'i' - signed, other - unsigned)
|
||||
template <typename T = void>
|
||||
class ArgConverter : public ArgVisitor<ArgConverter<T>, void> {
|
||||
private:
|
||||
internal::Arg &arg_;
|
||||
wchar_t type_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter);
|
||||
|
||||
public:
|
||||
ArgConverter(internal::Arg &arg, wchar_t type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void visit_bool(bool value) {
|
||||
if (type_ != 's')
|
||||
visit_any_int(value);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
void visit_any_int(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
using internal::Arg;
|
||||
typedef typename internal::Conditional<
|
||||
is_same<T, void>::value, U, T>::type TargetType;
|
||||
if (sizeof(TargetType) <= sizeof(int)) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_.type = Arg::INT;
|
||||
arg_.int_value = static_cast<int>(static_cast<TargetType>(value));
|
||||
} else {
|
||||
arg_.type = Arg::UINT;
|
||||
typedef typename internal::MakeUnsigned<TargetType>::Type Unsigned;
|
||||
arg_.uint_value = static_cast<unsigned>(static_cast<Unsigned>(value));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
arg_.type = Arg::LONG_LONG;
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_.long_long_value = static_cast<LongLong>(value);
|
||||
} else {
|
||||
arg_.type = Arg::ULONG_LONG;
|
||||
arg_.ulong_long_value =
|
||||
static_cast<typename internal::MakeUnsigned<U>::Type>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
class CharConverter : public ArgVisitor<CharConverter, void> {
|
||||
private:
|
||||
internal::Arg &arg_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter);
|
||||
|
||||
public:
|
||||
explicit CharConverter(internal::Arg &arg) : arg_(arg) {}
|
||||
|
||||
template <typename T>
|
||||
void visit_any_int(T value) {
|
||||
arg_.type = internal::Arg::CHAR;
|
||||
arg_.int_value = static_cast<char>(value);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename Char>
|
||||
class PrintfArgFormatter :
|
||||
public ArgFormatterBase<PrintfArgFormatter<Char>, Char> {
|
||||
|
||||
void write_null_pointer() {
|
||||
this->spec().type_ = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
typedef ArgFormatterBase<PrintfArgFormatter<Char>, Char> Base;
|
||||
|
||||
public:
|
||||
PrintfArgFormatter(BasicWriter<Char> &w, FormatSpec &s)
|
||||
: ArgFormatterBase<PrintfArgFormatter<Char>, Char>(w, s) {}
|
||||
|
||||
void visit_bool(bool value) {
|
||||
FormatSpec &fmt_spec = this->spec();
|
||||
if (fmt_spec.type_ != 's')
|
||||
return this->visit_any_int(value);
|
||||
fmt_spec.type_ = 0;
|
||||
this->write(value);
|
||||
}
|
||||
|
||||
void visit_char(int value) {
|
||||
const FormatSpec &fmt_spec = this->spec();
|
||||
BasicWriter<Char> &w = this->writer();
|
||||
if (fmt_spec.type_ && fmt_spec.type_ != 'c')
|
||||
w.write_int(value, fmt_spec);
|
||||
typedef typename BasicWriter<Char>::CharPtr CharPtr;
|
||||
CharPtr out = CharPtr();
|
||||
if (fmt_spec.width_ > 1) {
|
||||
Char fill = ' ';
|
||||
out = w.grow_buffer(fmt_spec.width_);
|
||||
if (fmt_spec.align_ != ALIGN_LEFT) {
|
||||
std::fill_n(out, fmt_spec.width_ - 1, fill);
|
||||
out += fmt_spec.width_ - 1;
|
||||
} else {
|
||||
std::fill_n(out + 1, fmt_spec.width_ - 1, fill);
|
||||
}
|
||||
} else {
|
||||
out = w.grow_buffer(1);
|
||||
}
|
||||
*out = static_cast<Char>(value);
|
||||
}
|
||||
|
||||
void visit_cstring(const char *value) {
|
||||
if (value)
|
||||
Base::visit_cstring(value);
|
||||
else if (this->spec().type_ == 'p')
|
||||
write_null_pointer();
|
||||
else
|
||||
this->write("(null)");
|
||||
}
|
||||
|
||||
void visit_pointer(const void *value) {
|
||||
if (value)
|
||||
return Base::visit_pointer(value);
|
||||
this->spec().type_ = 0;
|
||||
write_null_pointer();
|
||||
}
|
||||
|
||||
void visit_custom(Arg::CustomValue c) {
|
||||
BasicFormatter<Char> formatter(ArgList(), this->writer());
|
||||
const Char format_str[] = {'}', 0};
|
||||
const Char *format = format_str;
|
||||
c.format(&formatter, c.value, &format);
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace fmt
|
||||
|
||||
FMT_FUNC void fmt::SystemError::init(
|
||||
FMT_FUNC void SystemError::init(
|
||||
int err_code, CStringRef format_str, ArgList args) {
|
||||
error_code_ = err_code;
|
||||
MemoryWriter w;
|
||||
internal::format_system_error(w, err_code, format(format_str, args));
|
||||
format_system_error(w, err_code, format(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(w.str());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int fmt::internal::CharTraits<char>::format_float(
|
||||
int internal::CharTraits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format,
|
||||
unsigned width, int precision, T value) {
|
||||
if (width == 0) {
|
||||
@@ -454,7 +237,7 @@ int fmt::internal::CharTraits<char>::format_float(
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int fmt::internal::CharTraits<wchar_t>::format_float(
|
||||
int internal::CharTraits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
||||
unsigned width, int precision, T value) {
|
||||
if (width == 0) {
|
||||
@@ -468,7 +251,7 @@ int fmt::internal::CharTraits<wchar_t>::format_float(
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char fmt::internal::BasicData<T>::DIGITS[] =
|
||||
const char internal::BasicData<T>::DIGITS[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
@@ -487,40 +270,40 @@ const char fmt::internal::BasicData<T>::DIGITS[] =
|
||||
factor * 1000000000
|
||||
|
||||
template <typename T>
|
||||
const uint32_t fmt::internal::BasicData<T>::POWERS_OF_10_32[] = {
|
||||
const uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = {
|
||||
0, FMT_POWERS_OF_10(1)
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const uint64_t fmt::internal::BasicData<T>::POWERS_OF_10_64[] = {
|
||||
const uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = {
|
||||
0,
|
||||
FMT_POWERS_OF_10(1),
|
||||
FMT_POWERS_OF_10(fmt::ULongLong(1000000000)),
|
||||
FMT_POWERS_OF_10(ULongLong(1000000000)),
|
||||
// Multiply several constants instead of using a single long long constant
|
||||
// to avoid warnings about C++98 not supporting long long.
|
||||
fmt::ULongLong(1000000000) * fmt::ULongLong(1000000000) * 10
|
||||
ULongLong(1000000000) * ULongLong(1000000000) * 10
|
||||
};
|
||||
|
||||
FMT_FUNC void fmt::internal::report_unknown_type(char code, const char *type) {
|
||||
FMT_FUNC void internal::report_unknown_type(char code, const char *type) {
|
||||
(void)type;
|
||||
if (std::isprint(static_cast<unsigned char>(code))) {
|
||||
FMT_THROW(fmt::FormatError(
|
||||
fmt::format("unknown format code '{}' for {}", code, type)));
|
||||
FMT_THROW(FormatError(
|
||||
format("unknown format code '{}' for {}", code, type)));
|
||||
}
|
||||
FMT_THROW(fmt::FormatError(
|
||||
fmt::format("unknown format code '\\x{:02x}' for {}",
|
||||
FMT_THROW(FormatError(
|
||||
format("unknown format code '\\x{:02x}' for {}",
|
||||
static_cast<unsigned>(code), type)));
|
||||
}
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) {
|
||||
FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) {
|
||||
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16";
|
||||
if (s.size() > INT_MAX)
|
||||
FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG));
|
||||
int s_size = static_cast<int>(s.size());
|
||||
int length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, 0, 0);
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);
|
||||
if (length == 0)
|
||||
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
|
||||
buffer_.resize(length + 1);
|
||||
@@ -531,30 +314,31 @@ FMT_FUNC fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) {
|
||||
buffer_[length] = 0;
|
||||
}
|
||||
|
||||
FMT_FUNC fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) {
|
||||
FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(WindowsError(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
FMT_FUNC int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) {
|
||||
FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) {
|
||||
if (s.size() > INT_MAX)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, 0, 0, 0, 0);
|
||||
int length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, 0, 0);
|
||||
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_FUNC void fmt::WindowsError::init(
|
||||
FMT_FUNC void WindowsError::init(
|
||||
int err_code, CStringRef format_str, ArgList args) {
|
||||
error_code_ = err_code;
|
||||
MemoryWriter w;
|
||||
@@ -563,17 +347,17 @@ FMT_FUNC void fmt::WindowsError::init(
|
||||
base = std::runtime_error(w.str());
|
||||
}
|
||||
|
||||
FMT_FUNC void fmt::internal::format_windows_error(
|
||||
fmt::Writer &out, int error_code,
|
||||
fmt::StringRef message) FMT_NOEXCEPT {
|
||||
FMT_FUNC void internal::format_windows_error(
|
||||
Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer;
|
||||
buffer.resize(INLINE_BUFFER_SIZE);
|
||||
for (;;) {
|
||||
wchar_t *system_message = &buffer[0];
|
||||
int result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
0, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
system_message, static_cast<uint32_t>(buffer.size()), 0);
|
||||
int result = FormatMessageW(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL);
|
||||
if (result != 0) {
|
||||
UTF16ToUTF8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
@@ -592,12 +376,11 @@ FMT_FUNC void fmt::internal::format_windows_error(
|
||||
|
||||
#endif // FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC void fmt::internal::format_system_error(
|
||||
fmt::Writer &out, int error_code,
|
||||
fmt::StringRef message) FMT_NOEXCEPT {
|
||||
FMT_FUNC void format_system_error(
|
||||
Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer;
|
||||
buffer.resize(INLINE_BUFFER_SIZE);
|
||||
internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer;
|
||||
buffer.resize(internal::INLINE_BUFFER_SIZE);
|
||||
for (;;) {
|
||||
char *system_message = &buffer[0];
|
||||
int result = safe_strerror(error_code, system_message, buffer.size());
|
||||
@@ -614,11 +397,11 @@ FMT_FUNC void fmt::internal::format_system_error(
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void fmt::internal::ArgMap<Char>::init(const ArgList &args) {
|
||||
void internal::ArgMap<Char>::init(const ArgList &args) {
|
||||
if (!map_.empty())
|
||||
return;
|
||||
typedef internal::NamedArg<Char> NamedArg;
|
||||
const NamedArg *named_arg = 0;
|
||||
const NamedArg *named_arg = FMT_NULL;
|
||||
bool use_values =
|
||||
args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
|
||||
if (use_values) {
|
||||
@@ -659,18 +442,18 @@ void fmt::internal::ArgMap<Char>::init(const ArgList &args) {
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void fmt::internal::FixedBuffer<Char>::grow(std::size_t) {
|
||||
void internal::FixedBuffer<Char>::grow(std::size_t) {
|
||||
FMT_THROW(std::runtime_error("buffer overflow"));
|
||||
}
|
||||
|
||||
FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg(
|
||||
FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg(
|
||||
unsigned arg_index, const char *&error) {
|
||||
Arg arg = args_[arg_index];
|
||||
internal::Arg arg = args_[arg_index];
|
||||
switch (arg.type) {
|
||||
case Arg::NONE:
|
||||
case internal::Arg::NONE:
|
||||
error = "argument index out of range";
|
||||
break;
|
||||
case Arg::NAMED_ARG:
|
||||
case internal::Arg::NAMED_ARG:
|
||||
arg = *static_cast<const internal::Arg*>(arg.pointer);
|
||||
break;
|
||||
default:
|
||||
@@ -679,203 +462,31 @@ FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg(
|
||||
return arg;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void fmt::internal::PrintfFormatter<Char>::parse_flags(
|
||||
FormatSpec &spec, const Char *&s) {
|
||||
for (;;) {
|
||||
switch (*s++) {
|
||||
case '-':
|
||||
spec.align_ = ALIGN_LEFT;
|
||||
break;
|
||||
case '+':
|
||||
spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
|
||||
break;
|
||||
case '0':
|
||||
spec.fill_ = '0';
|
||||
break;
|
||||
case ' ':
|
||||
spec.flags_ |= SIGN_FLAG;
|
||||
break;
|
||||
case '#':
|
||||
spec.flags_ |= HASH_FLAG;
|
||||
break;
|
||||
default:
|
||||
--s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
Arg fmt::internal::PrintfFormatter<Char>::get_arg(
|
||||
const Char *s, unsigned arg_index) {
|
||||
(void)s;
|
||||
const char *error = 0;
|
||||
Arg arg = arg_index == UINT_MAX ?
|
||||
next_arg(error) : FormatterBase::get_arg(arg_index - 1, error);
|
||||
if (error)
|
||||
FMT_THROW(FormatError(!*s ? "invalid format string" : error));
|
||||
return arg;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
unsigned fmt::internal::PrintfFormatter<Char>::parse_header(
|
||||
const Char *&s, FormatSpec &spec) {
|
||||
unsigned arg_index = UINT_MAX;
|
||||
Char c = *s;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
unsigned value = parse_nonnegative_int(s);
|
||||
if (*s == '$') { // value is an argument index
|
||||
++s;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0')
|
||||
spec.fill_ = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
spec.width_ = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(spec, s);
|
||||
// Parse width.
|
||||
if (*s >= '0' && *s <= '9') {
|
||||
spec.width_ = parse_nonnegative_int(s);
|
||||
} else if (*s == '*') {
|
||||
++s;
|
||||
spec.width_ = WidthHandler(spec).visit(get_arg(s));
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void fmt::internal::PrintfFormatter<Char>::format(
|
||||
BasicWriter<Char> &writer, BasicCStringRef<Char> format_str) {
|
||||
const Char *start = format_str.c_str();
|
||||
const Char *s = start;
|
||||
while (*s) {
|
||||
Char c = *s++;
|
||||
if (c != '%') continue;
|
||||
if (*s == c) {
|
||||
write(writer, start, s);
|
||||
start = ++s;
|
||||
continue;
|
||||
}
|
||||
write(writer, start, s - 1);
|
||||
|
||||
FormatSpec spec;
|
||||
spec.align_ = ALIGN_RIGHT;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
unsigned arg_index = parse_header(s, spec);
|
||||
|
||||
// Parse precision.
|
||||
if (*s == '.') {
|
||||
++s;
|
||||
if ('0' <= *s && *s <= '9') {
|
||||
spec.precision_ = static_cast<int>(parse_nonnegative_int(s));
|
||||
} else if (*s == '*') {
|
||||
++s;
|
||||
spec.precision_ = PrecisionHandler().visit(get_arg(s));
|
||||
}
|
||||
}
|
||||
|
||||
Arg arg = get_arg(s, arg_index);
|
||||
if (spec.flag(HASH_FLAG) && IsZeroInt().visit(arg))
|
||||
spec.flags_ &= ~to_unsigned<int>(HASH_FLAG);
|
||||
if (spec.fill_ == '0') {
|
||||
if (arg.type <= Arg::LAST_NUMERIC_TYPE)
|
||||
spec.align_ = ALIGN_NUMERIC;
|
||||
else
|
||||
spec.fill_ = ' '; // Ignore '0' flag for non-numeric types.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
switch (*s++) {
|
||||
case 'h':
|
||||
if (*s == 'h')
|
||||
ArgConverter<signed char>(arg, *++s).visit(arg);
|
||||
else
|
||||
ArgConverter<short>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'l':
|
||||
if (*s == 'l')
|
||||
ArgConverter<fmt::LongLong>(arg, *++s).visit(arg);
|
||||
else
|
||||
ArgConverter<long>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'j':
|
||||
ArgConverter<intmax_t>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'z':
|
||||
ArgConverter<std::size_t>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 't':
|
||||
ArgConverter<std::ptrdiff_t>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--s;
|
||||
ArgConverter<void>(arg, *s).visit(arg);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (!*s)
|
||||
FMT_THROW(FormatError("invalid format string"));
|
||||
spec.type_ = static_cast<char>(*s++);
|
||||
if (arg.type <= Arg::LAST_INTEGER_TYPE) {
|
||||
// Normalize type.
|
||||
switch (spec.type_) {
|
||||
case 'i': case 'u':
|
||||
spec.type_ = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
// TODO: handle wchar_t
|
||||
CharConverter(arg).visit(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = s;
|
||||
|
||||
// Format argument.
|
||||
internal::PrintfArgFormatter<Char>(writer, spec).visit(arg);
|
||||
}
|
||||
write(writer, start, s);
|
||||
}
|
||||
|
||||
FMT_FUNC void fmt::report_system_error(
|
||||
FMT_FUNC void report_system_error(
|
||||
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
|
||||
// 'fmt::' is for bcc32.
|
||||
fmt::report_error(internal::format_system_error, error_code, message);
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
FMT_FUNC void fmt::report_windows_error(
|
||||
FMT_FUNC void report_windows_error(
|
||||
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
|
||||
// 'fmt::' is for bcc32.
|
||||
fmt::report_error(internal::format_windows_error, error_code, message);
|
||||
report_error(internal::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void fmt::print(std::FILE *f, CStringRef format_str, ArgList args) {
|
||||
FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) {
|
||||
MemoryWriter w;
|
||||
w.write(format_str, args);
|
||||
std::fwrite(w.data(), 1, w.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void fmt::print(CStringRef format_str, ArgList args) {
|
||||
FMT_FUNC void print(CStringRef format_str, ArgList args) {
|
||||
print(stdout, format_str, args);
|
||||
}
|
||||
|
||||
FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) {
|
||||
FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) {
|
||||
char escape[] = "\x1b[30m";
|
||||
escape[3] = static_cast<char>('0' + c);
|
||||
std::fputs(escape, stdout);
|
||||
@@ -883,53 +494,42 @@ FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) {
|
||||
std::fputs(RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
FMT_FUNC int fmt::fprintf(std::FILE *f, CStringRef format, ArgList args) {
|
||||
MemoryWriter w;
|
||||
printf(w, format, args);
|
||||
std::size_t size = w.size();
|
||||
return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast<int>(size);
|
||||
}
|
||||
|
||||
#ifndef FMT_HEADER_ONLY
|
||||
|
||||
template struct fmt::internal::BasicData<void>;
|
||||
template struct internal::BasicData<void>;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template void fmt::internal::FixedBuffer<char>::grow(std::size_t);
|
||||
template void internal::FixedBuffer<char>::grow(std::size_t);
|
||||
|
||||
template void fmt::internal::ArgMap<char>::init(const fmt::ArgList &args);
|
||||
template void internal::ArgMap<char>::init(const ArgList &args);
|
||||
|
||||
template void fmt::internal::PrintfFormatter<char>::format(
|
||||
BasicWriter<char> &writer, CStringRef format);
|
||||
|
||||
template int fmt::internal::CharTraits<char>::format_float(
|
||||
template FMT_API int internal::CharTraits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format,
|
||||
unsigned width, int precision, double value);
|
||||
|
||||
template int fmt::internal::CharTraits<char>::format_float(
|
||||
template FMT_API int internal::CharTraits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format,
|
||||
unsigned width, int precision, long double value);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template void fmt::internal::FixedBuffer<wchar_t>::grow(std::size_t);
|
||||
template void internal::FixedBuffer<wchar_t>::grow(std::size_t);
|
||||
|
||||
template void fmt::internal::ArgMap<wchar_t>::init(const fmt::ArgList &args);
|
||||
template void internal::ArgMap<wchar_t>::init(const ArgList &args);
|
||||
|
||||
template void fmt::internal::PrintfFormatter<wchar_t>::format(
|
||||
BasicWriter<wchar_t> &writer, WCStringRef format);
|
||||
|
||||
template int fmt::internal::CharTraits<wchar_t>::format_float(
|
||||
template FMT_API int internal::CharTraits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
||||
unsigned width, int precision, double value);
|
||||
|
||||
template int fmt::internal::CharTraits<wchar_t>::format_float(
|
||||
template FMT_API int internal::CharTraits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
||||
unsigned width, int precision, long double value);
|
||||
|
||||
#endif // FMT_HEADER_ONLY
|
||||
|
||||
} // namespace fmt
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
632
fmt/format.h
632
fmt/format.h
File diff suppressed because it is too large
Load Diff
@@ -4,34 +4,15 @@
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
namespace fmt {
|
||||
|
||||
namespace {
|
||||
// Write the content of w to os.
|
||||
void write(std::ostream &os, Writer &w) {
|
||||
namespace internal {
|
||||
FMT_FUNC void write(std::ostream &os, Writer &w) {
|
||||
const char *data = w.data();
|
||||
typedef internal::MakeUnsigned<std::streamsize>::Type UnsignedStreamSize;
|
||||
UnsignedStreamSize size = w.size();
|
||||
@@ -49,13 +30,6 @@ void write(std::ostream &os, Writer &w) {
|
||||
FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) {
|
||||
MemoryWriter w;
|
||||
w.write(format_str, args);
|
||||
write(os, w);
|
||||
}
|
||||
|
||||
FMT_FUNC int fprintf(std::ostream &os, CStringRef format, ArgList args) {
|
||||
MemoryWriter w;
|
||||
printf(w, format, args);
|
||||
write(os, w);
|
||||
return static_cast<int>(w.size());
|
||||
internal::write(os, w);
|
||||
}
|
||||
} // namespace fmt
|
||||
|
||||
@@ -4,25 +4,7 @@
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
@@ -42,28 +24,27 @@ class FormatBuf : public std::basic_streambuf<Char> {
|
||||
typedef typename std::basic_streambuf<Char>::traits_type traits_type;
|
||||
|
||||
Buffer<Char> &buffer_;
|
||||
Char *start_;
|
||||
|
||||
public:
|
||||
FormatBuf(Buffer<Char> &buffer) : buffer_(buffer), start_(&buffer[0]) {
|
||||
this->setp(start_, start_ + buffer_.capacity());
|
||||
}
|
||||
FormatBuf(Buffer<Char> &buffer) : buffer_(buffer) {}
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof())) {
|
||||
size_t buf_size = size();
|
||||
buffer_.resize(buf_size);
|
||||
buffer_.reserve(buf_size * 2);
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
start_ = &buffer_[0];
|
||||
start_[buf_size] = traits_type::to_char_type(ch);
|
||||
this->setp(start_+ buf_size + 1, start_ + buf_size * 2);
|
||||
}
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return to_unsigned(this->pptr() - start_);
|
||||
std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,19 +65,22 @@ struct ConvertToIntImpl<T, true> {
|
||||
value = sizeof(convert(get<DummyStream>() << get<T>())) == sizeof(No)
|
||||
};
|
||||
};
|
||||
|
||||
// Write the content of w to os.
|
||||
FMT_API void write(std::ostream &os, Writer &w);
|
||||
} // namespace internal
|
||||
|
||||
// Formats a value.
|
||||
template <typename Char, typename ArgFormatter, typename T>
|
||||
void format(BasicFormatter<Char, ArgFormatter> &f,
|
||||
const Char *&format_str, const T &value) {
|
||||
template <typename Char, typename ArgFormatter_, typename T>
|
||||
void format_arg(BasicFormatter<Char, ArgFormatter_> &f,
|
||||
const Char *&format_str, const T &value) {
|
||||
internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE> buffer;
|
||||
|
||||
internal::FormatBuf<Char> format_buf(buffer);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
output << value;
|
||||
|
||||
BasicStringRef<Char> str(&buffer[0], format_buf.size());
|
||||
BasicStringRef<Char> str(&buffer[0], buffer.size());
|
||||
typedef internal::MakeArg< BasicFormatter<Char> > MakeArg;
|
||||
format_str = f.format(format_str, MakeArg(str));
|
||||
}
|
||||
@@ -112,18 +96,6 @@ void format(BasicFormatter<Char, ArgFormatter> &f,
|
||||
*/
|
||||
FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args);
|
||||
FMT_VARIADIC(void, print, std::ostream &, CStringRef)
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_API int fprintf(std::ostream &os, CStringRef format_str, ArgList args);
|
||||
FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef)
|
||||
} // namespace fmt
|
||||
|
||||
#ifdef FMT_HEADER_ONLY
|
||||
|
||||
57
fmt/posix.cc
57
fmt/posix.cc
@@ -1,28 +1,10 @@
|
||||
/*
|
||||
A C++ interface to POSIX functions.
|
||||
|
||||
Copyright (c) 2014 - 2016, Victor Zverovich
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
@@ -39,6 +21,9 @@
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
|
||||
@@ -90,16 +75,16 @@ fmt::BufferedFile::BufferedFile(
|
||||
fmt::CStringRef filename, fmt::CStringRef mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0);
|
||||
if (!file_)
|
||||
throw SystemError(errno, "cannot open file {}", filename);
|
||||
FMT_THROW(SystemError(errno, "cannot open file {}", filename));
|
||||
}
|
||||
|
||||
void fmt::BufferedFile::close() {
|
||||
if (!file_)
|
||||
return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = 0;
|
||||
file_ = FMT_NULL;
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot close file");
|
||||
FMT_THROW(SystemError(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
@@ -108,7 +93,7 @@ void fmt::BufferedFile::close() {
|
||||
int fmt::BufferedFile::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1)
|
||||
throw SystemError(errno, "cannot get file descriptor");
|
||||
FMT_THROW(SystemError(errno, "cannot get file descriptor"));
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -121,7 +106,7 @@ fmt::File::File(fmt::CStringRef path, int oflag) {
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
#endif
|
||||
if (fd_ == -1)
|
||||
throw SystemError(errno, "cannot open file {}", path);
|
||||
FMT_THROW(SystemError(errno, "cannot open file {}", path));
|
||||
}
|
||||
|
||||
fmt::File::~File() FMT_NOEXCEPT {
|
||||
@@ -139,7 +124,7 @@ void fmt::File::close() {
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot close file");
|
||||
FMT_THROW(SystemError(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
fmt::LongLong fmt::File::size() const {
|
||||
@@ -153,7 +138,7 @@ fmt::LongLong fmt::File::size() const {
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
throw WindowsError(GetLastError(), "cannot get file size");
|
||||
FMT_THROW(WindowsError(GetLastError(), "cannot get file size"));
|
||||
}
|
||||
fmt::ULongLong long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
@@ -161,7 +146,7 @@ fmt::LongLong fmt::File::size() const {
|
||||
typedef struct stat Stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
throw SystemError(errno, "cannot get file attributes");
|
||||
FMT_THROW(SystemError(errno, "cannot get file attributes"));
|
||||
FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size),
|
||||
"return type of File::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
@@ -172,7 +157,7 @@ std::size_t fmt::File::read(void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
throw SystemError(errno, "cannot read from file");
|
||||
FMT_THROW(SystemError(errno, "cannot read from file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
@@ -180,7 +165,7 @@ std::size_t fmt::File::write(const void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
throw SystemError(errno, "cannot write to file");
|
||||
FMT_THROW(SystemError(errno, "cannot write to file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
@@ -189,7 +174,7 @@ fmt::File fmt::File::dup(int fd) {
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
throw SystemError(errno, "cannot duplicate file descriptor {}", fd);
|
||||
FMT_THROW(SystemError(errno, "cannot duplicate file descriptor {}", fd));
|
||||
return File(new_fd);
|
||||
}
|
||||
|
||||
@@ -197,8 +182,8 @@ void fmt::File::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
throw SystemError(errno,
|
||||
"cannot duplicate file descriptor {} to {}", fd_, fd);
|
||||
FMT_THROW(SystemError(errno,
|
||||
"cannot duplicate file descriptor {} to {}", fd_, fd));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +210,7 @@ void fmt::File::pipe(File &read_end, File &write_end) {
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
#endif
|
||||
if (result != 0)
|
||||
throw SystemError(errno, "cannot create pipe");
|
||||
FMT_THROW(SystemError(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = File(fds[0]);
|
||||
@@ -236,7 +221,7 @@ fmt::BufferedFile fmt::File::fdopen(const char *mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
if (!f)
|
||||
throw SystemError(errno, "cannot associate stream with file descriptor");
|
||||
FMT_THROW(SystemError(errno, "cannot associate stream with file descriptor"));
|
||||
BufferedFile file(f);
|
||||
fd_ = -1;
|
||||
return file;
|
||||
@@ -250,7 +235,7 @@ long fmt::getpagesize() {
|
||||
#else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0)
|
||||
throw SystemError(errno, "cannot get memory page size");
|
||||
FMT_THROW(SystemError(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
|
||||
96
fmt/posix.h
96
fmt/posix.h
@@ -1,34 +1,16 @@
|
||||
/*
|
||||
A C++ interface to POSIX functions.
|
||||
|
||||
Copyright (c) 2014 - 2016, Victor Zverovich
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#ifndef FMT_POSIX_H_
|
||||
#define FMT_POSIX_H_
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
@@ -41,7 +23,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
@@ -69,25 +51,6 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_GCC_VERSION >= 407
|
||||
# define FMT_UNUSED __attribute__((unused))
|
||||
#else
|
||||
# define FMT_UNUSED
|
||||
#endif
|
||||
|
||||
#ifndef FMT_USE_STATIC_ASSERT
|
||||
# define FMT_USE_STATIC_ASSERT 0
|
||||
#endif
|
||||
|
||||
#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \
|
||||
(FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600
|
||||
# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message)
|
||||
#else
|
||||
# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b)
|
||||
# define FMT_STATIC_ASSERT(cond, message) \
|
||||
typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
@@ -125,10 +88,10 @@ class BufferedFile {
|
||||
|
||||
public:
|
||||
// Constructs a BufferedFile object which doesn't represent any file.
|
||||
BufferedFile() FMT_NOEXCEPT : file_(0) {}
|
||||
BufferedFile() FMT_NOEXCEPT : file_(FMT_NULL) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
~BufferedFile() FMT_NOEXCEPT;
|
||||
FMT_API ~BufferedFile() FMT_NOEXCEPT;
|
||||
|
||||
#if !FMT_USE_RVALUE_REFERENCES
|
||||
// Emulate a move constructor and a move assignment operator if rvalue
|
||||
@@ -147,7 +110,7 @@ public:
|
||||
|
||||
// A "move constructor" for moving from an lvalue.
|
||||
BufferedFile(BufferedFile &f) FMT_NOEXCEPT : file_(f.file_) {
|
||||
f.file_ = 0;
|
||||
f.file_ = FMT_NULL;
|
||||
}
|
||||
|
||||
// A "move assignment operator" for moving from a temporary.
|
||||
@@ -161,7 +124,7 @@ public:
|
||||
BufferedFile &operator=(BufferedFile &other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = 0;
|
||||
other.file_ = FMT_NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -169,7 +132,7 @@ public:
|
||||
// BufferedFile file = BufferedFile(...);
|
||||
operator Proxy() FMT_NOEXCEPT {
|
||||
Proxy p = {file_};
|
||||
file_ = 0;
|
||||
file_ = FMT_NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -179,29 +142,29 @@ public:
|
||||
|
||||
public:
|
||||
BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = 0;
|
||||
other.file_ = FMT_NULL;
|
||||
}
|
||||
|
||||
BufferedFile& operator=(BufferedFile &&other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = 0;
|
||||
other.file_ = FMT_NULL;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Opens a file.
|
||||
BufferedFile(CStringRef filename, CStringRef mode);
|
||||
FMT_API BufferedFile(CStringRef filename, CStringRef mode);
|
||||
|
||||
// Closes the file.
|
||||
void close();
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE *get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
int (fileno)() const;
|
||||
FMT_API int (fileno)() const;
|
||||
|
||||
void print(CStringRef format_str, const ArgList &args) {
|
||||
fmt::print(file_, format_str, args);
|
||||
@@ -234,7 +197,7 @@ class File {
|
||||
File() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a File object representing this file.
|
||||
File(CStringRef path, int oflag);
|
||||
FMT_API File(CStringRef path, int oflag);
|
||||
|
||||
#if !FMT_USE_RVALUE_REFERENCES
|
||||
// Emulate a move constructor and a move assignment operator if rvalue
|
||||
@@ -297,49 +260,50 @@ class File {
|
||||
#endif
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
~File() FMT_NOEXCEPT;
|
||||
FMT_API ~File() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
void close();
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
LongLong size() const;
|
||||
FMT_API LongLong size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
std::size_t read(void *buffer, std::size_t count);
|
||||
FMT_API std::size_t read(void *buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
std::size_t write(const void *buffer, std::size_t count);
|
||||
FMT_API std::size_t write(const void *buffer, std::size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
static File dup(int fd);
|
||||
FMT_API static File dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
void dup2(int fd);
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT;
|
||||
FMT_API void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
static void pipe(File &read_end, File &write_end);
|
||||
FMT_API static void pipe(File &read_end, File &write_end);
|
||||
|
||||
// Creates a BufferedFile object associated with this file and detaches
|
||||
// this File object from the file.
|
||||
BufferedFile fdopen(const char *mode);
|
||||
FMT_API BufferedFile fdopen(const char *mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
#if defined(LC_NUMERIC_MASK) || defined(_MSC_VER)
|
||||
#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \
|
||||
!defined(__ANDROID__) && !defined(__CYGWIN__)
|
||||
# define FMT_LOCALE
|
||||
#endif
|
||||
|
||||
@@ -372,9 +336,9 @@ class Locale {
|
||||
public:
|
||||
typedef locale_t Type;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", NULL)) {
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) {
|
||||
if (!locale_)
|
||||
throw fmt::SystemError(errno, "cannot create locale");
|
||||
FMT_THROW(fmt::SystemError(errno, "cannot create locale"));
|
||||
}
|
||||
~Locale() { freelocale(locale_); }
|
||||
|
||||
@@ -383,7 +347,7 @@ class Locale {
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char *&str) const {
|
||||
char *end = 0;
|
||||
char *end = FMT_NULL;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
|
||||
32
fmt/printf.cc
Normal file
32
fmt/printf.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Formatting library for C++
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#include "format.h"
|
||||
#include "printf.h"
|
||||
|
||||
namespace fmt {
|
||||
|
||||
template <typename Char>
|
||||
void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format, ArgList args);
|
||||
|
||||
FMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) {
|
||||
MemoryWriter w;
|
||||
printf(w, format, args);
|
||||
std::size_t size = w.size();
|
||||
return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast<int>(size);
|
||||
}
|
||||
|
||||
#ifndef FMT_HEADER_ONLY
|
||||
|
||||
template void PrintfFormatter<char>::format(CStringRef format);
|
||||
template void PrintfFormatter<wchar_t>::format(WCStringRef format);
|
||||
|
||||
#endif // FMT_HEADER_ONLY
|
||||
|
||||
} // namespace fmt
|
||||
603
fmt/printf.h
Normal file
603
fmt/printf.h
Normal file
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
Formatting library for C++
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::fill_n
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
namespace fmt {
|
||||
namespace internal {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned>
|
||||
struct IntChecker {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
unsigned max = std::numeric_limits<int>::max();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IntChecker<true> {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
return value >= std::numeric_limits<int>::min() &&
|
||||
value <= std::numeric_limits<int>::max();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class PrecisionHandler : public ArgVisitor<PrecisionHandler, int> {
|
||||
public:
|
||||
void report_unhandled_arg() {
|
||||
FMT_THROW(FormatError("precision is not integer"));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int visit_any_int(T value) {
|
||||
if (!IntChecker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(FormatError("number is too big"));
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
};
|
||||
|
||||
// IsZeroInt::visit(arg) returns true iff arg is a zero integer.
|
||||
class IsZeroInt : public ArgVisitor<IsZeroInt, bool> {
|
||||
public:
|
||||
template <typename T>
|
||||
bool visit_any_int(T value) { return value == 0; }
|
||||
};
|
||||
|
||||
// returns the default type for format specific "%s"
|
||||
class DefaultType : public ArgVisitor<DefaultType, char> {
|
||||
public:
|
||||
char visit_char(int) { return 'c'; }
|
||||
|
||||
char visit_bool(bool) { return 's'; }
|
||||
|
||||
char visit_pointer(const void *) { return 'p'; }
|
||||
|
||||
template <typename T>
|
||||
char visit_any_int(T) { return 'd'; }
|
||||
|
||||
template <typename T>
|
||||
char visit_any_double(T) { return 'g'; }
|
||||
|
||||
char visit_unhandled_arg() { return 's'; }
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct is_same {
|
||||
enum { value = 0 };
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_same<T, T> {
|
||||
enum { value = 1 };
|
||||
};
|
||||
|
||||
// An argument visitor that converts an integer argument to T for printf,
|
||||
// if T is an integral type. If T is void, the argument is converted to
|
||||
// corresponding signed or unsigned type depending on the type specifier:
|
||||
// 'd' and 'i' - signed, other - unsigned)
|
||||
template <typename T = void>
|
||||
class ArgConverter : public ArgVisitor<ArgConverter<T>, void> {
|
||||
private:
|
||||
internal::Arg &arg_;
|
||||
wchar_t type_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter);
|
||||
|
||||
public:
|
||||
ArgConverter(internal::Arg &arg, wchar_t type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void visit_bool(bool value) {
|
||||
if (type_ != 's')
|
||||
visit_any_int(value);
|
||||
}
|
||||
|
||||
void visit_char(char value) {
|
||||
if (type_ != 's')
|
||||
visit_any_int(value);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
void visit_any_int(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
if (type_ == 's') {
|
||||
is_signed = std::numeric_limits<U>::is_signed;
|
||||
}
|
||||
|
||||
using internal::Arg;
|
||||
typedef typename internal::Conditional<
|
||||
is_same<T, void>::value, U, T>::type TargetType;
|
||||
if (sizeof(TargetType) <= sizeof(int)) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_.type = Arg::INT;
|
||||
arg_.int_value = static_cast<int>(static_cast<TargetType>(value));
|
||||
} else {
|
||||
arg_.type = Arg::UINT;
|
||||
typedef typename internal::MakeUnsigned<TargetType>::Type Unsigned;
|
||||
arg_.uint_value = static_cast<unsigned>(static_cast<Unsigned>(value));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
arg_.type = Arg::LONG_LONG;
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_.long_long_value = static_cast<LongLong>(value);
|
||||
} else {
|
||||
arg_.type = Arg::ULONG_LONG;
|
||||
arg_.ulong_long_value =
|
||||
static_cast<typename internal::MakeUnsigned<U>::Type>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
class CharConverter : public ArgVisitor<CharConverter, void> {
|
||||
private:
|
||||
internal::Arg &arg_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter);
|
||||
|
||||
public:
|
||||
explicit CharConverter(internal::Arg &arg) : arg_(arg) {}
|
||||
|
||||
template <typename T>
|
||||
void visit_any_int(T value) {
|
||||
arg_.type = internal::Arg::CHAR;
|
||||
arg_.int_value = static_cast<char>(value);
|
||||
}
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
class WidthHandler : public ArgVisitor<WidthHandler, unsigned> {
|
||||
private:
|
||||
FormatSpec &spec_;
|
||||
|
||||
FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler);
|
||||
|
||||
public:
|
||||
explicit WidthHandler(FormatSpec &spec) : spec_(spec) {}
|
||||
|
||||
void report_unhandled_arg() {
|
||||
FMT_THROW(FormatError("width is not integer"));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
unsigned visit_any_int(T value) {
|
||||
typedef typename internal::IntTraits<T>::MainType UnsignedType;
|
||||
UnsignedType width = static_cast<UnsignedType>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
spec_.align_ = ALIGN_LEFT;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = std::numeric_limits<int>::max();
|
||||
if (width > int_max)
|
||||
FMT_THROW(FormatError("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
\rst
|
||||
A ``printf`` argument formatter based on the `curiously recurring template
|
||||
pattern <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.
|
||||
|
||||
To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some
|
||||
or all of the visit methods with the same signatures as the methods in
|
||||
`~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`.
|
||||
Pass the subclass as the *Impl* template parameter. When a formatting
|
||||
function processes an argument, it will dispatch to a visit method
|
||||
specific to the argument type. For example, if the argument type is
|
||||
``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass
|
||||
will be called. If the subclass doesn't contain a method with this signature,
|
||||
then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its
|
||||
superclass will be called.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Impl, typename Char, typename Spec>
|
||||
class BasicPrintfArgFormatter :
|
||||
public internal::ArgFormatterBase<Impl, Char, Spec> {
|
||||
private:
|
||||
void write_null_pointer() {
|
||||
this->spec().type_ = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
typedef internal::ArgFormatterBase<Impl, Char, Spec> Base;
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*writer* is a reference to the output writer and *spec* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
BasicPrintfArgFormatter(BasicWriter<Char> &w, Spec &s)
|
||||
: internal::ArgFormatterBase<Impl, Char, Spec>(w, s) {}
|
||||
|
||||
/** Formats an argument of type ``bool``. */
|
||||
void visit_bool(bool value) {
|
||||
Spec &fmt_spec = this->spec();
|
||||
if (fmt_spec.type_ != 's')
|
||||
return this->visit_any_int(value);
|
||||
fmt_spec.type_ = 0;
|
||||
this->write(value);
|
||||
}
|
||||
|
||||
/** Formats a character. */
|
||||
void visit_char(int value) {
|
||||
const Spec &fmt_spec = this->spec();
|
||||
BasicWriter<Char> &w = this->writer();
|
||||
if (fmt_spec.type_ && fmt_spec.type_ != 'c')
|
||||
w.write_int(value, fmt_spec);
|
||||
typedef typename BasicWriter<Char>::CharPtr CharPtr;
|
||||
CharPtr out = CharPtr();
|
||||
if (fmt_spec.width_ > 1) {
|
||||
Char fill = ' ';
|
||||
out = w.grow_buffer(fmt_spec.width_);
|
||||
if (fmt_spec.align_ != ALIGN_LEFT) {
|
||||
std::fill_n(out, fmt_spec.width_ - 1, fill);
|
||||
out += fmt_spec.width_ - 1;
|
||||
} else {
|
||||
std::fill_n(out + 1, fmt_spec.width_ - 1, fill);
|
||||
}
|
||||
} else {
|
||||
out = w.grow_buffer(1);
|
||||
}
|
||||
*out = static_cast<Char>(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
void visit_cstring(const char *value) {
|
||||
if (value)
|
||||
Base::visit_cstring(value);
|
||||
else if (this->spec().type_ == 'p')
|
||||
write_null_pointer();
|
||||
else
|
||||
this->write("(null)");
|
||||
}
|
||||
|
||||
/** Formats a pointer. */
|
||||
void visit_pointer(const void *value) {
|
||||
if (value)
|
||||
return Base::visit_pointer(value);
|
||||
this->spec().type_ = 0;
|
||||
write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
void visit_custom(internal::Arg::CustomValue c) {
|
||||
BasicFormatter<Char> formatter(ArgList(), this->writer());
|
||||
const Char format_str[] = {'}', 0};
|
||||
const Char *format = format_str;
|
||||
c.format(&formatter, c.value, &format);
|
||||
}
|
||||
};
|
||||
|
||||
/** The default printf argument formatter. */
|
||||
template <typename Char>
|
||||
class PrintfArgFormatter :
|
||||
public BasicPrintfArgFormatter<PrintfArgFormatter<Char>, Char, FormatSpec> {
|
||||
public:
|
||||
/** Constructs an argument formatter object. */
|
||||
PrintfArgFormatter(BasicWriter<Char> &w, FormatSpec &s)
|
||||
: BasicPrintfArgFormatter<PrintfArgFormatter<Char>, Char, FormatSpec>(w, s) {}
|
||||
};
|
||||
|
||||
/** This template formats data and writes the output to a writer. */
|
||||
template <typename Char, typename ArgFormatter = PrintfArgFormatter<Char> >
|
||||
class PrintfFormatter : private internal::FormatterBase {
|
||||
private:
|
||||
BasicWriter<Char> &writer_;
|
||||
|
||||
void parse_flags(FormatSpec &spec, const Char *&s);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is equal
|
||||
// to the maximum unsigned value, the next argument.
|
||||
internal::Arg get_arg(
|
||||
const Char *s,
|
||||
unsigned arg_index = (std::numeric_limits<unsigned>::max)());
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
unsigned parse_header(const Char *&s, FormatSpec &spec);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``PrintfFormatter`` object. References to the arguments and
|
||||
the writer are stored in the formatter object so make sure they have
|
||||
appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
explicit PrintfFormatter(const ArgList &al, BasicWriter<Char> &w)
|
||||
: FormatterBase(al), writer_(w) {}
|
||||
|
||||
/** Formats stored arguments and writes the output to the writer. */
|
||||
void format(BasicCStringRef<Char> format_str);
|
||||
};
|
||||
|
||||
template <typename Char, typename AF>
|
||||
void PrintfFormatter<Char, AF>::parse_flags(FormatSpec &spec, const Char *&s) {
|
||||
for (;;) {
|
||||
switch (*s++) {
|
||||
case '-':
|
||||
spec.align_ = ALIGN_LEFT;
|
||||
break;
|
||||
case '+':
|
||||
spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
|
||||
break;
|
||||
case '0':
|
||||
spec.fill_ = '0';
|
||||
break;
|
||||
case ' ':
|
||||
spec.flags_ |= SIGN_FLAG;
|
||||
break;
|
||||
case '#':
|
||||
spec.flags_ |= HASH_FLAG;
|
||||
break;
|
||||
default:
|
||||
--s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename AF>
|
||||
internal::Arg PrintfFormatter<Char, AF>::get_arg(const Char *s,
|
||||
unsigned arg_index) {
|
||||
(void)s;
|
||||
const char *error = FMT_NULL;
|
||||
internal::Arg arg = arg_index == std::numeric_limits<unsigned>::max() ?
|
||||
next_arg(error) : FormatterBase::get_arg(arg_index - 1, error);
|
||||
if (error)
|
||||
FMT_THROW(FormatError(!*s ? "invalid format string" : error));
|
||||
return arg;
|
||||
}
|
||||
|
||||
template <typename Char, typename AF>
|
||||
unsigned PrintfFormatter<Char, AF>::parse_header(
|
||||
const Char *&s, FormatSpec &spec) {
|
||||
unsigned arg_index = std::numeric_limits<unsigned>::max();
|
||||
Char c = *s;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
unsigned value = internal::parse_nonnegative_int(s);
|
||||
if (*s == '$') { // value is an argument index
|
||||
++s;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0')
|
||||
spec.fill_ = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
spec.width_ = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(spec, s);
|
||||
// Parse width.
|
||||
if (*s >= '0' && *s <= '9') {
|
||||
spec.width_ = internal::parse_nonnegative_int(s);
|
||||
} else if (*s == '*') {
|
||||
++s;
|
||||
spec.width_ = internal::WidthHandler(spec).visit(get_arg(s));
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename Char, typename AF>
|
||||
void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
|
||||
const Char *start = format_str.c_str();
|
||||
const Char *s = start;
|
||||
while (*s) {
|
||||
Char c = *s++;
|
||||
if (c != '%') continue;
|
||||
if (*s == c) {
|
||||
write(writer_, start, s);
|
||||
start = ++s;
|
||||
continue;
|
||||
}
|
||||
write(writer_, start, s - 1);
|
||||
|
||||
FormatSpec spec;
|
||||
spec.align_ = ALIGN_RIGHT;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
unsigned arg_index = parse_header(s, spec);
|
||||
|
||||
// Parse precision.
|
||||
if (*s == '.') {
|
||||
++s;
|
||||
if ('0' <= *s && *s <= '9') {
|
||||
spec.precision_ = static_cast<int>(internal::parse_nonnegative_int(s));
|
||||
} else if (*s == '*') {
|
||||
++s;
|
||||
spec.precision_ = internal::PrecisionHandler().visit(get_arg(s));
|
||||
} else {
|
||||
spec.precision_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
using internal::Arg;
|
||||
Arg arg = get_arg(s, arg_index);
|
||||
if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg))
|
||||
spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG);
|
||||
if (spec.fill_ == '0') {
|
||||
if (arg.type <= Arg::LAST_NUMERIC_TYPE)
|
||||
spec.align_ = ALIGN_NUMERIC;
|
||||
else
|
||||
spec.fill_ = ' '; // Ignore '0' flag for non-numeric types.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
using internal::ArgConverter;
|
||||
switch (*s++) {
|
||||
case 'h':
|
||||
if (*s == 'h')
|
||||
ArgConverter<signed char>(arg, *++s).visit(arg);
|
||||
else
|
||||
ArgConverter<short>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'l':
|
||||
if (*s == 'l')
|
||||
ArgConverter<fmt::LongLong>(arg, *++s).visit(arg);
|
||||
else
|
||||
ArgConverter<long>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'j':
|
||||
ArgConverter<intmax_t>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'z':
|
||||
ArgConverter<std::size_t>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 't':
|
||||
ArgConverter<std::ptrdiff_t>(arg, *s).visit(arg);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--s;
|
||||
ArgConverter<void>(arg, *s).visit(arg);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (!*s)
|
||||
FMT_THROW(FormatError("invalid format string"));
|
||||
spec.type_ = static_cast<char>(*s++);
|
||||
|
||||
if (spec.type_ == 's') {
|
||||
// set the format type to the default if 's' is specified
|
||||
spec.type_ = internal::DefaultType().visit(arg);
|
||||
}
|
||||
|
||||
if (arg.type <= Arg::LAST_INTEGER_TYPE) {
|
||||
// Normalize type.
|
||||
switch (spec.type_) {
|
||||
case 'i': case 'u':
|
||||
spec.type_ = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
// TODO: handle wchar_t
|
||||
internal::CharConverter(arg).visit(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = s;
|
||||
|
||||
// Format argument.
|
||||
AF(writer_, spec).visit(arg);
|
||||
}
|
||||
write(writer_, start, s);
|
||||
}
|
||||
|
||||
inline void printf(Writer &w, CStringRef format, ArgList args) {
|
||||
PrintfFormatter<char>(args, w).format(format);
|
||||
}
|
||||
FMT_VARIADIC(void, printf, Writer &, CStringRef)
|
||||
|
||||
inline void printf(WWriter &w, WCStringRef format, ArgList args) {
|
||||
PrintfFormatter<wchar_t>(args, w).format(format);
|
||||
}
|
||||
FMT_VARIADIC(void, printf, WWriter &, WCStringRef)
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
inline std::string sprintf(CStringRef format, ArgList args) {
|
||||
MemoryWriter w;
|
||||
printf(w, format, args);
|
||||
return w.str();
|
||||
}
|
||||
FMT_VARIADIC(std::string, sprintf, CStringRef)
|
||||
|
||||
inline std::wstring sprintf(WCStringRef format, ArgList args) {
|
||||
WMemoryWriter w;
|
||||
printf(w, format, args);
|
||||
return w.str();
|
||||
}
|
||||
FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef)
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args);
|
||||
FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef)
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
inline int printf(CStringRef format, ArgList args) {
|
||||
return fprintf(stdout, format, args);
|
||||
}
|
||||
FMT_VARIADIC(int, printf, CStringRef)
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
inline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) {
|
||||
MemoryWriter w;
|
||||
printf(w, format_str, args);
|
||||
internal::write(os, w);
|
||||
return static_cast<int>(w.size());
|
||||
}
|
||||
FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef)
|
||||
} // namespace fmt
|
||||
|
||||
#ifdef FMT_HEADER_ONLY
|
||||
# include "printf.cc"
|
||||
#endif
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
||||
126
fmt/string.h
Normal file
126
fmt/string.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
Formatting library for C++ - string utilities
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#ifndef FMT_STRING_H_
|
||||
#define FMT_STRING_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
namespace fmt {
|
||||
|
||||
namespace internal {
|
||||
|
||||
// A buffer that stores data in ``std::basic_string``.
|
||||
template <typename Char, typename Allocator = std::allocator<Char> >
|
||||
class StringBuffer : public Buffer<Char> {
|
||||
public:
|
||||
typedef std::basic_string<Char, std::char_traits<Char>, Allocator> StringType;
|
||||
|
||||
private:
|
||||
StringType data_;
|
||||
|
||||
protected:
|
||||
virtual void grow(std::size_t size) FMT_OVERRIDE {
|
||||
data_.resize(size);
|
||||
this->ptr_ = &data_[0];
|
||||
this->capacity_ = size;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit StringBuffer(const Allocator &allocator = Allocator())
|
||||
: data_(allocator) {}
|
||||
|
||||
// Moves the data to ``str`` clearing the buffer.
|
||||
void move_to(StringType &str) {
|
||||
data_.resize(this->size_);
|
||||
str.swap(data_);
|
||||
this->capacity_ = this->size_ = 0;
|
||||
this->ptr_ = FMT_NULL;
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
\rst
|
||||
This class template provides operations for formatting and writing data
|
||||
into a character stream. The output is stored in a ``std::basic_string``
|
||||
that grows dynamically.
|
||||
|
||||
You can use one of the following typedefs for common character types
|
||||
and the standard allocator:
|
||||
|
||||
+---------------+----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+============================+
|
||||
| StringWriter | BasicStringWriter<char> |
|
||||
+---------------+----------------------------+
|
||||
| WStringWriter | BasicStringWriter<wchar_t> |
|
||||
+---------------+----------------------------+
|
||||
|
||||
**Example**::
|
||||
|
||||
StringWriter out;
|
||||
out << "The answer is " << 42 << "\n";
|
||||
|
||||
This will write the following output to the ``out`` object:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
The answer is 42
|
||||
|
||||
The output can be moved to a ``std::basic_string`` with ``out.move_to()``.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char, typename Allocator = std::allocator<Char> >
|
||||
class BasicStringWriter : public BasicWriter<Char> {
|
||||
private:
|
||||
internal::StringBuffer<Char, Allocator> buffer_;
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`fmt::BasicStringWriter` object.
|
||||
\endrst
|
||||
*/
|
||||
explicit BasicStringWriter(const Allocator &allocator = Allocator())
|
||||
: BasicWriter<Char>(buffer_), buffer_(allocator) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Moves the buffer content to *str* clearing the buffer.
|
||||
\endrst
|
||||
*/
|
||||
void move_to(std::basic_string<Char, std::char_traits<Char>, Allocator> &str) {
|
||||
buffer_.move_to(str);
|
||||
}
|
||||
};
|
||||
|
||||
typedef BasicStringWriter<char> StringWriter;
|
||||
typedef BasicStringWriter<wchar_t> WStringWriter;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Converts *value* to ``std::string`` using the default format for type *T*.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include "fmt/string.h"
|
||||
|
||||
std::string answer = fmt::to_string(42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
std::string to_string(const T &value) {
|
||||
fmt::MemoryWriter w;
|
||||
w << value;
|
||||
return w.str();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FMT_STRING_H_
|
||||
125
fmt/time.h
125
fmt/time.h
@@ -4,37 +4,25 @@
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#ifndef FMT_TIME_H_
|
||||
#define FMT_TIME_H_
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "format.h"
|
||||
#include <ctime>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4702) // unreachable code
|
||||
# pragma warning(disable: 4996) // "deprecated" functions
|
||||
#endif
|
||||
|
||||
namespace fmt {
|
||||
template <typename ArgFormatter>
|
||||
void format(BasicFormatter<char, ArgFormatter> &f,
|
||||
const char *&format_str, const std::tm &tm) {
|
||||
void format_arg(BasicFormatter<char, ArgFormatter> &f,
|
||||
const char *&format_str, const std::tm &tm) {
|
||||
if (*format_str == ':')
|
||||
++format_str;
|
||||
const char *end = format_str;
|
||||
@@ -54,11 +42,102 @@ void format(BasicFormatter<char, ArgFormatter> &f,
|
||||
buffer.resize(start + count);
|
||||
break;
|
||||
}
|
||||
if (size >= format.size() * 256) {
|
||||
// If the buffer is 256 times larger than the format string, assume
|
||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
||||
// better way to distinguish the two cases:
|
||||
// https://github.com/fmtlib/fmt/issues/367
|
||||
break;
|
||||
}
|
||||
const std::size_t MIN_GROWTH = 10;
|
||||
buffer.reserve(buffer.capacity() + size > MIN_GROWTH ? size : MIN_GROWTH);
|
||||
buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||
}
|
||||
format_str = end + 1;
|
||||
}
|
||||
|
||||
namespace internal{
|
||||
inline Null<> localtime_r(...) { return Null<>(); }
|
||||
inline Null<> localtime_s(...) { return Null<>(); }
|
||||
inline Null<> gmtime_r(...) { return Null<>(); }
|
||||
inline Null<> gmtime_s(...) { return Null<>(); }
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::localtime
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
struct LocalTime {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
LocalTime(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::Null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::Null<>) {
|
||||
using namespace fmt::internal;
|
||||
std::tm *tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
LocalTime lt(time);
|
||||
if (lt.run())
|
||||
return lt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(fmt::FormatError("time_t value out of range"));
|
||||
return std::tm();
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::gmtime
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
struct GMTime {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
GMTime(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::Null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::Null<>) {
|
||||
std::tm *tm = std::gmtime(&time_);
|
||||
if (tm != FMT_NULL) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
GMTime gt(time);
|
||||
if (gt.run())
|
||||
return gt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(fmt::FormatError("time_t value out of range"));
|
||||
return std::tm();
|
||||
}
|
||||
} //namespace fmt
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif // FMT_TIME_H_
|
||||
|
||||
@@ -10,22 +10,23 @@ platform = os.environ.get('PLATFORM')
|
||||
path = os.environ['PATH']
|
||||
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config]
|
||||
if build == 'mingw':
|
||||
cmake_command.append('-GMinGW Makefiles')
|
||||
build_command = ['mingw32-make', '-j4']
|
||||
test_command = ['mingw32-make', 'test']
|
||||
# Remove the path to Git bin directory from $PATH because it breaks MinGW config.
|
||||
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
|
||||
os.environ['PATH'] = r'C:\MinGW\bin;' + path
|
||||
cmake_command.append('-GMinGW Makefiles')
|
||||
build_command = ['mingw32-make', '-j4']
|
||||
test_command = ['mingw32-make', 'test']
|
||||
# Remove the path to Git bin directory from $PATH because it breaks
|
||||
# MinGW config.
|
||||
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
|
||||
os.environ['PATH'] = r'C:\MinGW\bin;' + path
|
||||
else:
|
||||
# Add MSBuild 14.0 to PATH as described in
|
||||
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
|
||||
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\14.0\Bin;' + path
|
||||
generator = 'Visual Studio 14 2015'
|
||||
if platform == 'x64':
|
||||
generator += ' Win64'
|
||||
cmake_command.append('-G' + generator)
|
||||
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
|
||||
test_command = ['ctest', '-C', config]
|
||||
# Add MSBuild 14.0 to PATH as described in
|
||||
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
|
||||
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\14.0\Bin;' + path
|
||||
generator = 'Visual Studio 14 2015'
|
||||
if platform == 'x64':
|
||||
generator += ' Win64'
|
||||
cmake_command.append('-G' + generator)
|
||||
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
|
||||
test_command = ['ctest', '-C', config]
|
||||
|
||||
check_call(cmake_command)
|
||||
check_call(build_command)
|
||||
|
||||
@@ -20,3 +20,7 @@ build_script:
|
||||
on_failure:
|
||||
- appveyor PushArtifact Testing/Temporary/LastTest.log
|
||||
- appveyor AddTest test
|
||||
|
||||
# Uncomment this to debug AppVeyor failures.
|
||||
#on_finish:
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
@@ -20,7 +20,14 @@ if (FMT_USE_CPP11)
|
||||
check_cxx_source_compiles("
|
||||
#include <unistd.h>
|
||||
int main() {}" FMT_CPP11_UNISTD_H)
|
||||
if (FMT_CPP11_CMATH AND FMT_CPP11_UNISTD_H)
|
||||
# Check if snprintf works with -std=c++11. It may not in MinGW.
|
||||
check_cxx_source_compiles("
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
char buffer[10];
|
||||
snprintf(buffer, 10, \"foo\");
|
||||
}" FMT_CPP11_SNPRINTF)
|
||||
if (FMT_CPP11_CMATH AND FMT_CPP11_UNISTD_H AND FMT_CPP11_SNPRINTF)
|
||||
set(CPP11_FLAG -std=c++11)
|
||||
else ()
|
||||
check_cxx_compiler_flag(-std=gnu++11 HAVE_STD_GNUPP11_FLAG)
|
||||
@@ -37,6 +44,11 @@ if (FMT_USE_CPP11)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_STANDARD)
|
||||
# Don't use -std compiler flag if CMAKE_CXX_STANDARD is specified.
|
||||
set(CPP11_FLAG )
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS ${CPP11_FLAG})
|
||||
|
||||
# Check if variadic templates are working and not affected by GCC bug 39653:
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# A file downloader.
|
||||
|
||||
import contextlib, os, tempfile, timer, urllib2, urlparse
|
||||
|
||||
class Downloader:
|
||||
def __init__(self, dir=None):
|
||||
self.dir = dir
|
||||
|
||||
# Downloads a file and removes it when exiting a block.
|
||||
# Usage:
|
||||
# d = Downloader()
|
||||
# with d.download(url) as f:
|
||||
# use_file(f)
|
||||
def download(self, url, cookie=None):
|
||||
suffix = os.path.splitext(urlparse.urlsplit(url)[2])[1]
|
||||
fd, filename = tempfile.mkstemp(suffix=suffix, dir=self.dir)
|
||||
os.close(fd)
|
||||
with timer.print_time('Downloading', url, 'to', filename):
|
||||
opener = urllib2.build_opener()
|
||||
if cookie:
|
||||
opener.addheaders.append(('Cookie', cookie))
|
||||
num_tries = 2
|
||||
for i in range(num_tries):
|
||||
try:
|
||||
f = opener.open(url)
|
||||
except urllib2.URLError, e:
|
||||
print('Failed to open url', url)
|
||||
continue
|
||||
length = f.headers.get('content-length')
|
||||
if not length:
|
||||
print('Failed to get content-length')
|
||||
continue
|
||||
length = int(length)
|
||||
with open(filename, 'wb') as out:
|
||||
count = 0
|
||||
while count < length:
|
||||
data = f.read(1024 * 1024)
|
||||
count += len(data)
|
||||
out.write(data)
|
||||
@contextlib.contextmanager
|
||||
def remove(filename):
|
||||
try:
|
||||
yield filename
|
||||
finally:
|
||||
os.remove(filename)
|
||||
return remove(filename)
|
||||
235
support/manage.py
Executable file
235
support/manage.py
Executable file
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Manage site and releases.
|
||||
|
||||
Usage:
|
||||
manage.py release [<branch>]
|
||||
manage.py site
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime, docopt, fileinput, json, os
|
||||
import re, requests, shutil, sys, tempfile
|
||||
from contextlib import contextmanager
|
||||
from distutils.version import LooseVersion
|
||||
from subprocess import check_call
|
||||
|
||||
|
||||
class Git:
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
|
||||
def call(self, method, args, **kwargs):
|
||||
return check_call(['git', method] + list(args), **kwargs)
|
||||
|
||||
def add(self, *args):
|
||||
return self.call('add', args, cwd=self.dir)
|
||||
|
||||
def checkout(self, *args):
|
||||
return self.call('checkout', args, cwd=self.dir)
|
||||
|
||||
def clean(self, *args):
|
||||
return self.call('clean', args, cwd=self.dir)
|
||||
|
||||
def clone(self, *args):
|
||||
return self.call('clone', list(args) + [self.dir])
|
||||
|
||||
def commit(self, *args):
|
||||
return self.call('commit', args, cwd=self.dir)
|
||||
|
||||
def pull(self, *args):
|
||||
return self.call('pull', args, cwd=self.dir)
|
||||
|
||||
def push(self, *args):
|
||||
return self.call('push', args, cwd=self.dir)
|
||||
|
||||
def reset(self, *args):
|
||||
return self.call('reset', args, cwd=self.dir)
|
||||
|
||||
def update(self, *args):
|
||||
clone = not os.path.exists(self.dir)
|
||||
if clone:
|
||||
self.clone(*args)
|
||||
return clone
|
||||
|
||||
|
||||
def clean_checkout(repo, branch):
|
||||
repo.clean('-f', '-d')
|
||||
repo.reset('--hard')
|
||||
repo.checkout(branch)
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self, cwd):
|
||||
self.cwd = cwd
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
|
||||
check_call(args, **kwargs)
|
||||
|
||||
|
||||
def create_build_env():
|
||||
"""Create a build environment."""
|
||||
class Env:
|
||||
pass
|
||||
env = Env()
|
||||
|
||||
# Import the documentation build module.
|
||||
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(env.fmt_dir, 'doc'))
|
||||
import build
|
||||
|
||||
env.build_dir = 'build'
|
||||
|
||||
# Virtualenv and repos are cached to speed up builds.
|
||||
build.create_build_env(os.path.join(env.build_dir, 'virtualenv'))
|
||||
|
||||
env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
|
||||
return env
|
||||
|
||||
|
||||
@contextmanager
|
||||
def rewrite(filename):
|
||||
class Buffer:
|
||||
pass
|
||||
buffer = Buffer()
|
||||
if not os.path.exists(filename):
|
||||
buffer.data = ''
|
||||
yield buffer
|
||||
return
|
||||
with open(filename) as f:
|
||||
buffer.data = f.read()
|
||||
yield buffer
|
||||
with open(filename, 'w') as f:
|
||||
f.write(buffer.data)
|
||||
|
||||
|
||||
fmt_repo_url = 'git@github.com:fmtlib/fmt'
|
||||
|
||||
|
||||
def update_site(env):
|
||||
env.fmt_repo.update(fmt_repo_url)
|
||||
|
||||
doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io'))
|
||||
doc_repo.update('git@github.com:fmtlib/fmtlib.github.io')
|
||||
|
||||
for version in ['1.0.0', '1.1.0', '2.0.0', '3.0.0']:
|
||||
clean_checkout(env.fmt_repo, version)
|
||||
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
|
||||
# Remove the old theme.
|
||||
for entry in os.listdir(target_doc_dir):
|
||||
path = os.path.join(target_doc_dir, entry)
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
# Copy the new theme.
|
||||
for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap',
|
||||
'conf.py', 'fmt.less']:
|
||||
src = os.path.join(env.fmt_dir, 'doc', entry)
|
||||
dst = os.path.join(target_doc_dir, entry)
|
||||
copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile
|
||||
copy(src, dst)
|
||||
# Rename index to contents.
|
||||
contents = os.path.join(target_doc_dir, 'contents.rst')
|
||||
if not os.path.exists(contents):
|
||||
os.rename(os.path.join(target_doc_dir, 'index.rst'), contents)
|
||||
# Fix issues in reference.rst/api.rst.
|
||||
for filename in ['reference.rst', 'api.rst']:
|
||||
pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M)
|
||||
with rewrite(os.path.join(target_doc_dir, filename)) as b:
|
||||
b.data = b.data.replace('std::ostream &', 'std::ostream&')
|
||||
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
|
||||
b.data = b.data.replace('std::FILE*', 'std::FILE *')
|
||||
b.data = b.data.replace('unsigned int', 'unsigned')
|
||||
# Fix a broken link in index.rst.
|
||||
index = os.path.join(target_doc_dir, 'index.rst')
|
||||
with rewrite(index) as b:
|
||||
b.data = b.data.replace(
|
||||
'doc/latest/index.html#format-string-syntax', 'syntax.html')
|
||||
# Build the docs.
|
||||
html_dir = os.path.join(env.build_dir, 'html')
|
||||
if os.path.exists(html_dir):
|
||||
shutil.rmtree(html_dir)
|
||||
include_dir = env.fmt_repo.dir
|
||||
if LooseVersion(version) >= LooseVersion('3.0.0'):
|
||||
include_dir = os.path.join(include_dir, 'fmt')
|
||||
import build
|
||||
build.build_docs(version, doc_dir=target_doc_dir,
|
||||
include_dir=include_dir, work_dir=env.build_dir)
|
||||
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
|
||||
# Create symlinks for older versions.
|
||||
for link, target in {'index': 'contents', 'api': 'reference'}.items():
|
||||
link = os.path.join(html_dir, link) + '.html'
|
||||
target += '.html'
|
||||
if os.path.exists(os.path.join(html_dir, target)) and \
|
||||
not os.path.exists(link):
|
||||
os.symlink(target, link)
|
||||
# Copy docs to the website.
|
||||
version_doc_dir = os.path.join(doc_repo.dir, version)
|
||||
shutil.rmtree(version_doc_dir)
|
||||
shutil.move(html_dir, version_doc_dir)
|
||||
|
||||
|
||||
def release(args):
|
||||
env = create_build_env()
|
||||
fmt_repo = env.fmt_repo
|
||||
|
||||
branch = args.get('<branch>')
|
||||
if branch is None:
|
||||
branch = 'master'
|
||||
if not fmt_repo.update('-b', branch, fmt_repo_url):
|
||||
clean_checkout(fmt_repo, branch)
|
||||
|
||||
# Convert changelog from RST to GitHub-flavored Markdown and get the
|
||||
# version.
|
||||
changelog = 'ChangeLog.rst'
|
||||
changelog_path = os.path.join(fmt_repo.dir, changelog)
|
||||
import rst2md
|
||||
changes, version = rst2md.convert(changelog_path)
|
||||
cmakelists = 'CMakeLists.txt'
|
||||
for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists),
|
||||
inplace=True):
|
||||
prefix = 'set(FMT_VERSION '
|
||||
if line.startswith(prefix):
|
||||
line = prefix + version + ')\n'
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Update the version in the changelog.
|
||||
title_len = 0
|
||||
for line in fileinput.input(changelog_path, inplace=True):
|
||||
if line.decode('utf-8').startswith(version + ' - TBD'):
|
||||
line = version + ' - ' + datetime.date.today().isoformat()
|
||||
title_len = len(line)
|
||||
line += '\n'
|
||||
elif title_len:
|
||||
line = '-' * title_len + '\n'
|
||||
title_len = 0
|
||||
sys.stdout.write(line)
|
||||
# TODO: add new version to manage.py
|
||||
fmt_repo.checkout('-B', 'release')
|
||||
fmt_repo.add(changelog, cmakelists)
|
||||
fmt_repo.commit('-m', 'Update version')
|
||||
|
||||
# Build the docs and package.
|
||||
run = Runner(fmt_repo.dir)
|
||||
run('cmake', '.')
|
||||
run('make', 'doc', 'package_source')
|
||||
|
||||
update_site(env)
|
||||
|
||||
# Create a release on GitHub.
|
||||
fmt_repo.push('origin', 'release')
|
||||
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
|
||||
params={'access_token': os.getenv('FMT_TOKEN')},
|
||||
data=json.dumps({'tag_name': version,
|
||||
'target_commitish': 'release',
|
||||
'body': changes, 'draft': True}))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to create a release ' + str(r))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt.docopt(__doc__)
|
||||
if args.get('release'):
|
||||
release(args)
|
||||
elif args.get('site'):
|
||||
update_site(create_build_env())
|
||||
@@ -1,193 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Create a release.
|
||||
|
||||
Usage:
|
||||
release.py [<branch>]
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime, docopt, fileinput, json, os, re, requests, shutil, sys, tempfile
|
||||
from docutils import nodes, writers, core
|
||||
from subprocess import check_call
|
||||
|
||||
class MDWriter(writers.Writer):
|
||||
"""GitHub-flavored markdown writer"""
|
||||
|
||||
supported = ('md',)
|
||||
"""Formats this writer supports."""
|
||||
|
||||
def translate(self):
|
||||
translator = Translator(self.document)
|
||||
self.document.walkabout(translator)
|
||||
self.output = (translator.output, translator.version)
|
||||
|
||||
|
||||
def is_github_ref(node):
|
||||
return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
|
||||
|
||||
|
||||
class Translator(nodes.NodeVisitor):
|
||||
def __init__(self, document):
|
||||
nodes.NodeVisitor.__init__(self, document)
|
||||
self.output = ''
|
||||
self.indent = 0
|
||||
self.preserve_newlines = False
|
||||
|
||||
def write(self, text):
|
||||
self.output += text.replace('\n', '\n' + ' ' * self.indent)
|
||||
|
||||
def visit_document(self, node):
|
||||
pass
|
||||
|
||||
def depart_document(self, node):
|
||||
pass
|
||||
|
||||
def visit_section(self, node):
|
||||
pass
|
||||
|
||||
def depart_section(self, node):
|
||||
# Skip all sections except the first one.
|
||||
raise nodes.StopTraversal
|
||||
|
||||
def visit_title(self, node):
|
||||
self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def depart_title(self, node):
|
||||
pass
|
||||
|
||||
def visit_Text(self, node):
|
||||
if not self.preserve_newlines:
|
||||
node = node.replace('\n', ' ')
|
||||
self.write(node)
|
||||
|
||||
def depart_Text(self, node):
|
||||
pass
|
||||
|
||||
def visit_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def depart_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def visit_list_item(self, node):
|
||||
self.write('* ')
|
||||
self.indent += 2
|
||||
|
||||
def depart_list_item(self, node):
|
||||
self.indent -= 2
|
||||
self.write('\n\n')
|
||||
|
||||
def visit_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def depart_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def visit_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('[')
|
||||
|
||||
def depart_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('](' + node['refuri'] + ')')
|
||||
|
||||
def visit_target(self, node):
|
||||
pass
|
||||
|
||||
def depart_target(self, node):
|
||||
pass
|
||||
|
||||
def visit_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def depart_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
self.write('\n\n```')
|
||||
if 'c++' in node['classes']:
|
||||
self.write('c++')
|
||||
self.write('\n')
|
||||
self.preserve_newlines = True
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
self.write('\n```\n')
|
||||
self.preserve_newlines = False
|
||||
|
||||
def visit_inline(self, node):
|
||||
pass
|
||||
|
||||
def depart_inline(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self):
|
||||
self.cwd = '.'
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
|
||||
check_call(args, **kwargs)
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt.docopt(__doc__)
|
||||
workdir = tempfile.mkdtemp()
|
||||
try:
|
||||
run = Runner()
|
||||
fmt_dir = os.path.join(workdir, 'fmt')
|
||||
branch = args.get('<branch>')
|
||||
if branch is None:
|
||||
branch = 'master'
|
||||
run('git', 'clone', '-b', branch, 'git@github.com:fmtlib/fmt.git', fmt_dir)
|
||||
|
||||
# Convert changelog from RST to GitHub-flavored Markdown and get the version.
|
||||
changelog = 'ChangeLog.rst'
|
||||
changelog_path = os.path.join(fmt_dir, changelog)
|
||||
changes, version = core.publish_file(source_path=changelog_path, writer=MDWriter())
|
||||
cmakelists = 'CMakeLists.txt'
|
||||
for line in fileinput.input(os.path.join(fmt_dir, cmakelists), inplace=True):
|
||||
prefix = 'set(FMT_VERSION '
|
||||
if line.startswith(prefix):
|
||||
line = prefix + version + ')\n'
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Update the version in the changelog.
|
||||
title_len = 0
|
||||
for line in fileinput.input(changelog_path, inplace=True):
|
||||
if line.decode('utf-8').startswith(version + ' - TBD'):
|
||||
line = version + ' - ' + datetime.date.today().isoformat()
|
||||
title_len = len(line)
|
||||
line += '\n'
|
||||
elif title_len:
|
||||
line = '-' * title_len + '\n'
|
||||
title_len = 0
|
||||
sys.stdout.write(line)
|
||||
run.cwd = fmt_dir
|
||||
run('git', 'checkout', '-b', 'release')
|
||||
run('git', 'add', changelog, cmakelists)
|
||||
run('git', 'commit', '-m', 'Update version')
|
||||
|
||||
# Build the docs and package.
|
||||
run('cmake', '.')
|
||||
run('make', 'doc', 'package_source')
|
||||
site_dir = os.path.join(workdir, 'fmtlib.github.io')
|
||||
run('git', 'clone', 'git@github.com:fmtlib/fmtlib.github.io.git', site_dir)
|
||||
doc_dir = os.path.join(site_dir, version)
|
||||
shutil.copytree(os.path.join(fmt_dir, 'doc', 'html'), doc_dir,
|
||||
ignore=shutil.ignore_patterns('.doctrees', '.buildinfo'))
|
||||
run.cwd = site_dir
|
||||
run('git', 'add', doc_dir)
|
||||
run('git', 'commit', '-m', 'Update docs')
|
||||
|
||||
# Create a release on GitHub.
|
||||
run('git', 'push', 'origin', 'release', cwd=fmt_dir)
|
||||
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
|
||||
params={'access_token': os.getenv('FMT_TOKEN')},
|
||||
data=json.dumps({'tag_name': version, 'target_commitish': 'release',
|
||||
'body': changes, 'draft': True}))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to create a release ' + str(r))
|
||||
finally:
|
||||
shutil.rmtree(workdir)
|
||||
127
support/rst2md.py
Normal file
127
support/rst2md.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# reStructuredText (RST) to GitHub-flavored Markdown converter
|
||||
|
||||
import re
|
||||
from docutils import core, nodes, writers
|
||||
|
||||
|
||||
def is_github_ref(node):
|
||||
return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
|
||||
|
||||
|
||||
class Translator(nodes.NodeVisitor):
|
||||
def __init__(self, document):
|
||||
nodes.NodeVisitor.__init__(self, document)
|
||||
self.output = ''
|
||||
self.indent = 0
|
||||
self.preserve_newlines = False
|
||||
|
||||
def write(self, text):
|
||||
self.output += text.replace('\n', '\n' + ' ' * self.indent)
|
||||
|
||||
def visit_document(self, node):
|
||||
pass
|
||||
|
||||
def depart_document(self, node):
|
||||
pass
|
||||
|
||||
def visit_section(self, node):
|
||||
pass
|
||||
|
||||
def depart_section(self, node):
|
||||
# Skip all sections except the first one.
|
||||
raise nodes.StopTraversal
|
||||
|
||||
def visit_title(self, node):
|
||||
self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def depart_title(self, node):
|
||||
pass
|
||||
|
||||
def visit_Text(self, node):
|
||||
if not self.preserve_newlines:
|
||||
node = node.replace('\n', ' ')
|
||||
self.write(node)
|
||||
|
||||
def depart_Text(self, node):
|
||||
pass
|
||||
|
||||
def visit_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def depart_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def visit_list_item(self, node):
|
||||
self.write('* ')
|
||||
self.indent += 2
|
||||
|
||||
def depart_list_item(self, node):
|
||||
self.indent -= 2
|
||||
self.write('\n\n')
|
||||
|
||||
def visit_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def depart_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def visit_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('[')
|
||||
|
||||
def depart_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('](' + node['refuri'] + ')')
|
||||
|
||||
def visit_target(self, node):
|
||||
pass
|
||||
|
||||
def depart_target(self, node):
|
||||
pass
|
||||
|
||||
def visit_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def depart_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
self.write('\n\n```')
|
||||
if 'c++' in node['classes']:
|
||||
self.write('c++')
|
||||
self.write('\n')
|
||||
self.preserve_newlines = True
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
self.write('\n```\n')
|
||||
self.preserve_newlines = False
|
||||
|
||||
def visit_inline(self, node):
|
||||
pass
|
||||
|
||||
def depart_inline(self, node):
|
||||
pass
|
||||
|
||||
def visit_image(self, node):
|
||||
self.write('')
|
||||
|
||||
def depart_image(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class MDWriter(writers.Writer):
|
||||
"""GitHub-flavored markdown writer"""
|
||||
|
||||
supported = ('md',)
|
||||
"""Formats this writer supports."""
|
||||
|
||||
def translate(self):
|
||||
translator = Translator(self.document)
|
||||
self.document.walkabout(translator)
|
||||
self.output = (translator.output, translator.version)
|
||||
|
||||
|
||||
def convert(rst_path):
|
||||
"""Converts RST file to Markdown."""
|
||||
return core.publish_file(source_path=rst_path, writer=MDWriter())
|
||||
@@ -1,35 +0,0 @@
|
||||
# A with statement based timer.
|
||||
|
||||
from __future__ import print_function
|
||||
from contextlib import contextmanager
|
||||
import timeit
|
||||
|
||||
class Timer:
|
||||
"""
|
||||
A with statement based timer.
|
||||
Usage:
|
||||
t = Timer()
|
||||
with t:
|
||||
do_something()
|
||||
time = t.time
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
self.start = timeit.default_timer()
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
finish = timeit.default_timer()
|
||||
self.time = finish - self.start
|
||||
|
||||
@contextmanager
|
||||
def print_time(*args):
|
||||
"""
|
||||
Measures and prints the time taken to execute nested code.
|
||||
args: Additional arguments to print.
|
||||
"""
|
||||
t = Timer()
|
||||
print(*args)
|
||||
with t:
|
||||
yield
|
||||
print(*args, end=' ')
|
||||
print('finished in {0:.2f} second(s)'.format(t.time))
|
||||
@@ -6,34 +6,28 @@ import errno, os, re, shutil, sys, tempfile, urllib
|
||||
from subprocess import call, check_call, check_output, Popen, PIPE, STDOUT
|
||||
|
||||
def rmtree_if_exists(dir):
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
|
||||
def makedirs_if_not_exist(dir):
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
build = os.environ['BUILD']
|
||||
if build == 'Doc':
|
||||
travis = 'TRAVIS' in os.environ
|
||||
# Install dependencies.
|
||||
if travis:
|
||||
def install_dependencies():
|
||||
branch = os.environ['TRAVIS_BRANCH']
|
||||
if branch != 'master':
|
||||
print('Branch: ' + branch)
|
||||
exit(0) # Ignore non-master branches
|
||||
check_call('curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | ' +
|
||||
'sudo apt-key add -', shell=True)
|
||||
check_call('echo "deb https://deb.nodesource.com/node_0.10 precise main" | ' +
|
||||
'sudo tee /etc/apt/sources.list.d/nodesource.list', shell=True)
|
||||
print('Branch: ' + branch)
|
||||
exit(0) # Ignore non-master branches
|
||||
check_call('curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key ' +
|
||||
'| sudo apt-key add -', shell=True)
|
||||
check_call('echo "deb https://deb.nodesource.com/node_0.10 precise main" ' +
|
||||
'| sudo tee /etc/apt/sources.list.d/nodesource.list', shell=True)
|
||||
check_call(['sudo', 'apt-get', 'update'])
|
||||
check_call(['sudo', 'apt-get', 'install', 'python-virtualenv', 'nodejs'])
|
||||
check_call(['npm', 'install', '-g', 'less', 'less-plugin-clean-css'])
|
||||
@@ -41,39 +35,48 @@ if build == 'Doc':
|
||||
urllib.urlretrieve('http://mirrors.kernel.org/ubuntu/pool/main/d/doxygen/' +
|
||||
deb_file, deb_file)
|
||||
check_call(['sudo', 'dpkg', '-i', deb_file])
|
||||
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
||||
import build
|
||||
html_dir = build.build_docs()
|
||||
repo = 'fmtlib.github.io'
|
||||
if travis and 'KEY' not in os.environ:
|
||||
# Don't update the repo if building on Travis from an account that doesn't
|
||||
# have push access.
|
||||
print('Skipping update of ' + repo)
|
||||
exit(0)
|
||||
# Clone the fmtlib.github.io repo.
|
||||
rmtree_if_exists(repo)
|
||||
git_url = 'https://github.com/' if travis else 'git@github.com:'
|
||||
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
||||
# Copy docs to the repo.
|
||||
target_dir = os.path.join(repo, 'dev')
|
||||
rmtree_if_exists(target_dir)
|
||||
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
||||
if travis:
|
||||
check_call(['git', 'config', '--global', 'user.name', 'amplbot'])
|
||||
check_call(['git', 'config', '--global', 'user.email', 'viz@ampl.com'])
|
||||
# Push docs to GitHub pages.
|
||||
check_call(['git', 'add', '--all'], cwd=repo)
|
||||
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
||||
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
||||
cmd = 'git push'
|
||||
|
||||
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
build = os.environ['BUILD']
|
||||
if build == 'Doc':
|
||||
travis = 'TRAVIS' in os.environ
|
||||
if travis:
|
||||
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
||||
# Print the output without the key.
|
||||
print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
|
||||
if p.returncode != 0:
|
||||
raise CalledProcessError(p.returncode, cmd)
|
||||
exit(0)
|
||||
install_dependencies()
|
||||
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
||||
import build
|
||||
build.create_build_env()
|
||||
html_dir = build.build_docs()
|
||||
repo = 'fmtlib.github.io'
|
||||
if travis and 'KEY' not in os.environ:
|
||||
# Don't update the repo if building on Travis from an account that
|
||||
# doesn't have push access.
|
||||
print('Skipping update of ' + repo)
|
||||
exit(0)
|
||||
# Clone the fmtlib.github.io repo.
|
||||
rmtree_if_exists(repo)
|
||||
git_url = 'https://github.com/' if travis else 'git@github.com:'
|
||||
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
||||
# Copy docs to the repo.
|
||||
target_dir = os.path.join(repo, 'dev')
|
||||
rmtree_if_exists(target_dir)
|
||||
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
||||
if travis:
|
||||
check_call(['git', 'config', '--global', 'user.name', 'amplbot'])
|
||||
check_call(['git', 'config', '--global', 'user.email', 'viz@ampl.com'])
|
||||
# Push docs to GitHub pages.
|
||||
check_call(['git', 'add', '--all'], cwd=repo)
|
||||
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
||||
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
||||
cmd = 'git push'
|
||||
if travis:
|
||||
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
||||
# Print the output without the key.
|
||||
print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
|
||||
if p.returncode != 0:
|
||||
raise CalledProcessError(p.returncode, cmd)
|
||||
exit(0)
|
||||
|
||||
standard = os.environ['STANDARD']
|
||||
install_dir = os.path.join(fmt_dir, "_install")
|
||||
@@ -83,11 +86,13 @@ test_build_dir = os.path.join(fmt_dir, "_build_test")
|
||||
# Configure library.
|
||||
makedirs_if_not_exist(build_dir)
|
||||
common_cmake_flags = [
|
||||
'-DCMAKE_INSTALL_PREFIX=' + install_dir, '-DCMAKE_BUILD_TYPE=' + build
|
||||
'-DCMAKE_INSTALL_PREFIX=' + install_dir, '-DCMAKE_BUILD_TYPE=' + build
|
||||
]
|
||||
extra_cmake_flags = []
|
||||
if standard != '0x':
|
||||
extra_cmake_flags = ['-DCMAKE_CXX_FLAGS=-std=c++' + standard, '-DFMT_USE_CPP11=OFF']
|
||||
extra_cmake_flags = [
|
||||
'-DCMAKE_CXX_FLAGS=-std=c++' + standard, '-DFMT_USE_CPP11=OFF'
|
||||
]
|
||||
check_call(['cmake', '-DFMT_DOC=OFF', '-DFMT_PEDANTIC=ON', fmt_dir] +
|
||||
common_cmake_flags + extra_cmake_flags, cwd=build_dir)
|
||||
|
||||
@@ -98,9 +103,9 @@ check_call(['make', '-j4'], cwd=build_dir)
|
||||
env = os.environ.copy()
|
||||
env['CTEST_OUTPUT_ON_FAILURE'] = '1'
|
||||
if call(['make', 'test'], env=env, cwd=build_dir):
|
||||
with open('Testing/Temporary/LastTest.log', 'r') as f:
|
||||
print(f.read())
|
||||
sys.exit(-1)
|
||||
with open('Testing/Temporary/LastTest.log', 'r') as f:
|
||||
print(f.read())
|
||||
sys.exit(-1)
|
||||
|
||||
# Install library.
|
||||
check_call(['make', 'install'], cwd=build_dir)
|
||||
|
||||
@@ -8,23 +8,23 @@ import shutil, tempfile
|
||||
from subprocess import check_output, STDOUT
|
||||
|
||||
class Git:
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
|
||||
def __call__(self, *args):
|
||||
output = check_output(['git'] + list(args), cwd=self.dir, stderr=STDOUT)
|
||||
print(output)
|
||||
return output
|
||||
def __call__(self, *args):
|
||||
output = check_output(['git'] + list(args), cwd=self.dir, stderr=STDOUT)
|
||||
print(output)
|
||||
return output
|
||||
|
||||
dir = tempfile.mkdtemp()
|
||||
try:
|
||||
git = Git(dir)
|
||||
git('clone', '-b', 'coverity', 'git@github.com:fmtlib/fmt.git', dir)
|
||||
output = git('merge', '-X', 'theirs', '--no-commit', 'origin/master')
|
||||
if 'Fast-forward' not in output:
|
||||
git('reset', 'HEAD', '.travis.yml')
|
||||
git('checkout', '--', '.travis.yml')
|
||||
git('commit', '-m', 'Update coverity branch')
|
||||
git('push')
|
||||
git = Git(dir)
|
||||
git('clone', '-b', 'coverity', 'git@github.com:fmtlib/fmt.git', dir)
|
||||
output = git('merge', '-X', 'theirs', '--no-commit', 'origin/master')
|
||||
if 'Fast-forward' not in output:
|
||||
git('reset', 'HEAD', '.travis.yml')
|
||||
git('checkout', '--', '.travis.yml')
|
||||
git('commit', '-m', 'Update coverity branch')
|
||||
git('push')
|
||||
finally:
|
||||
shutil.rmtree(dir)
|
||||
shutil.rmtree(dir)
|
||||
|
||||
@@ -56,11 +56,19 @@ if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
|
||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros)
|
||||
endif ()
|
||||
|
||||
function(add_fmt_executable name)
|
||||
add_executable(${name} ${ARGN})
|
||||
if (MINGW)
|
||||
target_link_libraries(${name} -static-libgcc -static-libstdc++)
|
||||
endif ()
|
||||
endfunction()
|
||||
|
||||
# Adds a test.
|
||||
# Usage: add_fmt_test(name srcs...)
|
||||
function(add_fmt_test name)
|
||||
add_executable(${name} ${name}.cc ${ARGN})
|
||||
add_fmt_executable(${name} ${name}.cc ${ARGN})
|
||||
target_link_libraries(${name} test-main)
|
||||
|
||||
# define if certain c++ features can be used
|
||||
target_compile_definitions(${name} PRIVATE
|
||||
FMT_USE_TYPE_TRAITS=$<BOOL:${SUPPORTS_TYPE_TRAITS}>
|
||||
@@ -72,13 +80,17 @@ function(add_fmt_test name)
|
||||
endfunction()
|
||||
|
||||
add_fmt_test(assert-test)
|
||||
add_fmt_test(container-test)
|
||||
add_fmt_test(gtest-extra-test)
|
||||
add_fmt_test(format-test)
|
||||
add_fmt_test(format-impl-test)
|
||||
add_fmt_test(ostream-test)
|
||||
add_fmt_test(printf-test)
|
||||
add_fmt_test(string-test)
|
||||
add_fmt_test(time-test)
|
||||
add_fmt_test(util-test mock-allocator.h)
|
||||
add_fmt_test(macro-test)
|
||||
add_fmt_test(custom-formatter-test)
|
||||
|
||||
# Enable stricter options for one test to make sure that the header is free of
|
||||
# warnings.
|
||||
@@ -87,7 +99,8 @@ if (FMT_PEDANTIC AND MSVC)
|
||||
endif ()
|
||||
|
||||
if (HAVE_OPEN)
|
||||
add_executable(posix-mock-test posix-mock-test.cc ../fmt/format.cc ${TEST_MAIN_SRC})
|
||||
add_fmt_executable(posix-mock-test
|
||||
posix-mock-test.cc ../fmt/format.cc ../fmt/printf.cc ${TEST_MAIN_SRC})
|
||||
target_include_directories(posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR})
|
||||
target_compile_definitions(posix-mock-test PRIVATE FMT_USE_FILE_DESCRIPTORS=1)
|
||||
target_link_libraries(posix-mock-test gmock)
|
||||
@@ -95,7 +108,7 @@ if (HAVE_OPEN)
|
||||
add_fmt_test(posix-test)
|
||||
endif ()
|
||||
|
||||
add_executable(header-only-test
|
||||
add_fmt_executable(header-only-test
|
||||
header-only-test.cc header-only-test2.cc test-main.cc)
|
||||
target_link_libraries(header-only-test gmock)
|
||||
if (TARGET fmt-header-only)
|
||||
@@ -109,6 +122,7 @@ endif ()
|
||||
check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG)
|
||||
if (HAVE_FNO_EXCEPTIONS_FLAG)
|
||||
add_library(noexception-test ../fmt/format.cc)
|
||||
target_include_directories(noexception-test PRIVATE ${PROJECT_SOURCE_DIR})
|
||||
target_compile_options(noexception-test PRIVATE -fno-exceptions)
|
||||
endif ()
|
||||
|
||||
@@ -116,6 +130,7 @@ if (FMT_PEDANTIC)
|
||||
# Test that the library compiles without windows.h.
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
add_library(no-windows-h-test ../fmt/format.cc)
|
||||
target_include_directories(no-windows-h-test PRIVATE ${PROJECT_SOURCE_DIR})
|
||||
target_compile_definitions(no-windows-h-test PRIVATE FMT_USE_WINDOWS_H=0)
|
||||
endif ()
|
||||
|
||||
@@ -125,7 +140,7 @@ if (FMT_PEDANTIC)
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/compile-test"
|
||||
--build-generator ${CMAKE_GENERATOR}
|
||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||
--build-options
|
||||
--build-options
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCPP11_FLAG=${CPP11_FLAG}"
|
||||
"-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}")
|
||||
|
||||
@@ -5,9 +5,9 @@ project(fmt-test)
|
||||
add_subdirectory(../.. fmt)
|
||||
|
||||
add_executable(library-test "main.cc")
|
||||
target_link_libraries(library-test fmt)
|
||||
target_link_libraries(library-test fmt::fmt)
|
||||
|
||||
if (TARGET fmt-header-only)
|
||||
if (TARGET fmt::fmt-header-only)
|
||||
add_executable(header-only-test "main.cc")
|
||||
target_link_libraries(header-only-test fmt-header-only)
|
||||
target_link_libraries(header-only-test fmt::fmt-header-only)
|
||||
endif ()
|
||||
|
||||
94
test/container-test.cc
Normal file
94
test/container-test.cc
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Tests of container utilities
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#include "fmt/container.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using fmt::internal::ContainerBuffer;
|
||||
|
||||
TEST(ContainerBufferTest, Empty) {
|
||||
std::string data;
|
||||
ContainerBuffer<std::string> buffer(data);
|
||||
EXPECT_EQ(0u, buffer.size());
|
||||
EXPECT_EQ(0u, buffer.capacity());
|
||||
}
|
||||
|
||||
TEST(ContainerBufferTest, Reserve) {
|
||||
std::string data;
|
||||
ContainerBuffer<std::string> buffer(data);
|
||||
std::size_t capacity = std::string().capacity() + 10;
|
||||
buffer.reserve(capacity);
|
||||
EXPECT_EQ(0u, buffer.size());
|
||||
EXPECT_EQ(capacity, buffer.capacity());
|
||||
}
|
||||
|
||||
TEST(ContainerBufferTest, Resize) {
|
||||
std::string data;
|
||||
ContainerBuffer<std::string> buffer(data);
|
||||
std::size_t size = std::string().capacity() + 10;
|
||||
buffer.resize(size);
|
||||
EXPECT_EQ(size, buffer.size());
|
||||
EXPECT_EQ(size, buffer.capacity());
|
||||
}
|
||||
|
||||
TEST(ContainerBufferTest, Append) {
|
||||
std::string data("Why so");
|
||||
const std::string serious(" serious");
|
||||
ContainerBuffer<std::string> buffer(data);
|
||||
buffer.append(serious.c_str(), serious.c_str() + serious.length());
|
||||
EXPECT_EQ("Why so serious", data);
|
||||
EXPECT_EQ(data.length(), buffer.size());
|
||||
}
|
||||
|
||||
TEST(BasicContainerWriterTest, String) {
|
||||
std::string data;
|
||||
fmt::BasicContainerWriter<std::string> out(data);
|
||||
out << "The answer is " << 42 << "\n";
|
||||
EXPECT_EQ("The answer is 42\n", data);
|
||||
EXPECT_EQ(17u, out.size());
|
||||
}
|
||||
|
||||
TEST(BasicContainerWriterTest, WString) {
|
||||
std::wstring data;
|
||||
fmt::BasicContainerWriter<std::wstring> out(data);
|
||||
out << "The answer is " << 42 << "\n";
|
||||
EXPECT_EQ(L"The answer is 42\n", data);
|
||||
EXPECT_EQ(17u, out.size());
|
||||
}
|
||||
|
||||
TEST(BasicContainerWriterTest, Vector) {
|
||||
std::vector<char> data;
|
||||
fmt::BasicContainerWriter<std::vector<char> > out(data);
|
||||
out << "The answer is " << 42 << "\n";
|
||||
EXPECT_EQ(17u, data.size());
|
||||
EXPECT_EQ(out.size(), data.size());
|
||||
}
|
||||
|
||||
TEST(BasicContainerWriterTest, StringAppend) {
|
||||
std::string data("The");
|
||||
fmt::BasicContainerWriter<std::string> out(data);
|
||||
EXPECT_EQ(3u, data.size());
|
||||
EXPECT_EQ(3u, out.size());
|
||||
out << " answer is " << 42 << "\n";
|
||||
EXPECT_EQ("The answer is 42\n", data);
|
||||
EXPECT_EQ(17u, out.size());
|
||||
}
|
||||
|
||||
TEST(BasicContainerWriterTest, VectorAppend) {
|
||||
std::vector<char> data;
|
||||
data.push_back('T');
|
||||
data.push_back('h');
|
||||
data.push_back('e');
|
||||
fmt::BasicContainerWriter<std::vector<char> > out(data);
|
||||
EXPECT_EQ(3u, data.size());
|
||||
EXPECT_EQ(3u, out.size());
|
||||
out << " answer is " << 42 << "\n";
|
||||
EXPECT_EQ(17u, data.size());
|
||||
EXPECT_EQ(17u, out.size());
|
||||
}
|
||||
68
test/custom-formatter-test.cc
Normal file
68
test/custom-formatter-test.cc
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Custom argument formatter tests
|
||||
|
||||
Copyright (c) 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#include "fmt/printf.h"
|
||||
#include "gtest-extra.h"
|
||||
|
||||
using fmt::BasicPrintfArgFormatter;
|
||||
|
||||
// A custom argument formatter that doesn't print `-` for floating-point values
|
||||
// rounded to 0.
|
||||
class CustomArgFormatter
|
||||
: public fmt::BasicArgFormatter<CustomArgFormatter, char> {
|
||||
public:
|
||||
CustomArgFormatter(fmt::BasicFormatter<char, CustomArgFormatter> &f,
|
||||
fmt::FormatSpec &s, const char *fmt)
|
||||
: fmt::BasicArgFormatter<CustomArgFormatter, char>(f, s, fmt) {}
|
||||
|
||||
void visit_double(double value) {
|
||||
if (round(value * pow(10, spec().precision())) == 0)
|
||||
value = 0;
|
||||
fmt::BasicArgFormatter<CustomArgFormatter, char>::visit_double(value);
|
||||
}
|
||||
};
|
||||
|
||||
// A custom argument formatter that doesn't print `-` for floating-point values
|
||||
// rounded to 0.
|
||||
class CustomPrintfArgFormatter :
|
||||
public BasicPrintfArgFormatter<CustomPrintfArgFormatter, char> {
|
||||
public:
|
||||
typedef BasicPrintfArgFormatter<CustomPrintfArgFormatter, char> Base;
|
||||
|
||||
CustomPrintfArgFormatter(fmt::BasicWriter<char> &w, fmt::FormatSpec &spec)
|
||||
: Base(w, spec) {}
|
||||
|
||||
void visit_double(double value) {
|
||||
if (round(value * pow(10, spec().precision())) == 0)
|
||||
value = 0;
|
||||
Base::visit_double(value);
|
||||
}
|
||||
};
|
||||
|
||||
std::string custom_format(const char *format_str, fmt::ArgList args) {
|
||||
fmt::MemoryWriter writer;
|
||||
// Pass custom argument formatter as a template arg to BasicFormatter.
|
||||
fmt::BasicFormatter<char, CustomArgFormatter> formatter(args, writer);
|
||||
formatter.format(format_str);
|
||||
return writer.str();
|
||||
}
|
||||
FMT_VARIADIC(std::string, custom_format, const char *)
|
||||
|
||||
std::string custom_sprintf(const char* format_str, fmt::ArgList args){
|
||||
fmt::MemoryWriter writer;
|
||||
fmt::PrintfFormatter<char, CustomPrintfArgFormatter> formatter(args, writer);
|
||||
formatter.format(format_str);
|
||||
return writer.str();
|
||||
}
|
||||
FMT_VARIADIC(std::string, custom_sprintf, const char*);
|
||||
|
||||
TEST(CustomFormatterTest, Format) {
|
||||
EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001));
|
||||
EXPECT_EQ("0.00", custom_sprintf("%.2f", -.00001));
|
||||
}
|
||||
@@ -5,9 +5,9 @@ project(fmt-test)
|
||||
find_package(FMT REQUIRED)
|
||||
|
||||
add_executable(library-test main.cc)
|
||||
target_link_libraries(library-test fmt)
|
||||
target_link_libraries(library-test fmt::fmt)
|
||||
|
||||
if (TARGET fmt-header-only)
|
||||
if (TARGET fmt::fmt-header-only)
|
||||
add_executable(header-only-test main.cc)
|
||||
target_link_libraries(header-only-test fmt-header-only)
|
||||
target_link_libraries(header-only-test fmt::fmt-header-only)
|
||||
endif ()
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
*/
|
||||
|
||||
#define FMT_NOEXCEPT
|
||||
#undef FMT_SHARED
|
||||
#include "test-assert.h"
|
||||
|
||||
// Include format.cc instead of format.h to test implementation-specific stuff.
|
||||
// Include *.cc instead of *.h to test implementation-specific stuff.
|
||||
#include "fmt/format.cc"
|
||||
#include "fmt/printf.cc"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -46,7 +48,7 @@ TEST(FormatTest, ArgConverter) {
|
||||
Arg arg = Arg();
|
||||
arg.type = Arg::LONG_LONG;
|
||||
arg.long_long_value = std::numeric_limits<fmt::LongLong>::max();
|
||||
fmt::ArgConverter<fmt::LongLong>(arg, 'd').visit(arg);
|
||||
fmt::internal::ArgConverter<fmt::LongLong>(arg, 'd').visit(arg);
|
||||
EXPECT_EQ(Arg::LONG_LONG, arg.type);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,23 @@
|
||||
// Test that the library compiles if None is defined to 0 as done by xlib.h.
|
||||
#define None 0
|
||||
|
||||
struct LocaleMock {
|
||||
static LocaleMock *instance;
|
||||
|
||||
MOCK_METHOD0(localeconv, lconv *());
|
||||
} *LocaleMock::instance;
|
||||
|
||||
namespace fmt {
|
||||
namespace std {
|
||||
using namespace ::std;
|
||||
lconv *localeconv() {
|
||||
return LocaleMock::instance ?
|
||||
LocaleMock::instance->localeconv() : ::std::localeconv();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/time.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "mock-allocator.h"
|
||||
@@ -235,7 +250,7 @@ TEST(WriterTest, Allocator) {
|
||||
std::size_t size =
|
||||
static_cast<std::size_t>(1.5 * fmt::internal::INLINE_BUFFER_SIZE);
|
||||
std::vector<char> mem(size);
|
||||
EXPECT_CALL(alloc, allocate(size)).WillOnce(testing::Return(&mem[0]));
|
||||
EXPECT_CALL(alloc, allocate(size, 0)).WillOnce(testing::Return(&mem[0]));
|
||||
for (int i = 0; i < fmt::internal::INLINE_BUFFER_SIZE + 1; ++i)
|
||||
w << '*';
|
||||
EXPECT_CALL(alloc, deallocate(&mem[0], size));
|
||||
@@ -917,7 +932,7 @@ TEST(FormatterTest, RuntimeWidth) {
|
||||
FormatError, "number is too big");
|
||||
EXPECT_THROW_MSG(format("{0:{1}}", 0, -1l),
|
||||
FormatError, "negative width");
|
||||
if (fmt::internal::check(sizeof(long) > sizeof(int))) {
|
||||
if (fmt::internal::const_check(sizeof(long) > sizeof(int))) {
|
||||
long value = INT_MAX;
|
||||
EXPECT_THROW_MSG(format("{0:{1}}", 0, (value + 1)),
|
||||
FormatError, "number is too big");
|
||||
@@ -1036,7 +1051,7 @@ TEST(FormatterTest, RuntimePrecision) {
|
||||
FormatError, "number is too big");
|
||||
EXPECT_THROW_MSG(format("{0:.{1}}", 0, -1l),
|
||||
FormatError, "negative precision");
|
||||
if (fmt::internal::check(sizeof(long) > sizeof(int))) {
|
||||
if (fmt::internal::const_check(sizeof(long) > sizeof(int))) {
|
||||
long value = INT_MAX;
|
||||
EXPECT_THROW_MSG(format("{0:.{1}}", 0, (value + 1)),
|
||||
FormatError, "number is too big");
|
||||
@@ -1209,13 +1224,24 @@ TEST(FormatterTest, FormatOct) {
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatIntLocale) {
|
||||
#ifndef _WIN32
|
||||
const char *locale = "en_US.utf-8";
|
||||
#else
|
||||
const char *locale = "English_United States";
|
||||
#endif
|
||||
std::setlocale(LC_ALL, locale);
|
||||
EXPECT_EQ("1,234,567", format("{:n}", 1234567));
|
||||
ScopedMock<LocaleMock> mock;
|
||||
lconv lc = lconv();
|
||||
char sep[] = "--";
|
||||
lc.thousands_sep = sep;
|
||||
EXPECT_CALL(mock, localeconv()).Times(3).WillRepeatedly(testing::Return(&lc));
|
||||
EXPECT_EQ("123", format("{:n}", 123));
|
||||
EXPECT_EQ("1--234", format("{:n}", 1234));
|
||||
EXPECT_EQ("1--234--567", format("{:n}", 1234567));
|
||||
}
|
||||
|
||||
struct ConvertibleToLongLong {
|
||||
operator fmt::LongLong() const {
|
||||
return fmt::LongLong(1) << 32;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatConvertibleToLongLong) {
|
||||
EXPECT_EQ("100000000", format("{:x}", ConvertibleToLongLong()));
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatFloat) {
|
||||
@@ -1327,6 +1353,8 @@ TEST(FormatterTest, FormatUCharString) {
|
||||
EXPECT_EQ("test", format("{0:s}", str));
|
||||
const unsigned char *const_str = str;
|
||||
EXPECT_EQ("test", format("{0:s}", const_str));
|
||||
unsigned char *ptr = str;
|
||||
EXPECT_EQ("test", format("{0:s}", ptr));
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatPointer) {
|
||||
@@ -1350,7 +1378,7 @@ TEST(FormatterTest, FormatCStringRef) {
|
||||
EXPECT_EQ("test", format("{0}", CStringRef("test")));
|
||||
}
|
||||
|
||||
void format(fmt::BasicFormatter<char> &f, const char *, const Date &d) {
|
||||
void format_arg(fmt::BasicFormatter<char> &f, const char *, const Date &d) {
|
||||
f.writer() << d.year() << '-' << d.month() << '-' << d.day();
|
||||
}
|
||||
|
||||
@@ -1363,7 +1391,7 @@ TEST(FormatterTest, FormatCustom) {
|
||||
class Answer {};
|
||||
|
||||
template <typename Char>
|
||||
void format(fmt::BasicFormatter<Char> &f, const Char *, Answer) {
|
||||
void format_arg(fmt::BasicFormatter<Char> &f, const Char *, Answer) {
|
||||
f.writer() << "42";
|
||||
}
|
||||
|
||||
@@ -1534,13 +1562,25 @@ TEST(FormatTest, Variadic) {
|
||||
EXPECT_EQ(L"abc1", format(L"{}c{}", L"ab", 1));
|
||||
}
|
||||
|
||||
TEST(FormatTest, Time) {
|
||||
std::tm tm = std::tm();
|
||||
tm.tm_year = 116;
|
||||
tm.tm_mon = 3;
|
||||
tm.tm_mday = 25;
|
||||
EXPECT_EQ("The date is 2016-04-25.",
|
||||
fmt::format("The date is {:%Y-%m-%d}.", tm));
|
||||
TEST(FormatTest, JoinArg) {
|
||||
using fmt::join;
|
||||
int v1[3] = { 1, 2, 3 };
|
||||
std::vector<float> v2;
|
||||
v2.push_back(1.2f);
|
||||
v2.push_back(3.4f);
|
||||
|
||||
EXPECT_EQ("(1, 2, 3)", format("({})", join(v1 + 0, v1 + 3, ", ")));
|
||||
EXPECT_EQ("(1)", format("({})", join(v1 + 0, v1 + 1, ", ")));
|
||||
EXPECT_EQ("()", format("({})", join(v1 + 0, v1 + 0, ", ")));
|
||||
EXPECT_EQ("(001, 002, 003)", format("({:03})", join(v1 + 0, v1 + 3, ", ")));
|
||||
EXPECT_EQ("(+01.20, +03.40)", format("({:+06.2f})", join(v2.begin(), v2.end(), ", ")));
|
||||
|
||||
EXPECT_EQ(L"(1, 2, 3)", format(L"({})", join(v1 + 0, v1 + 3, L", ")));
|
||||
|
||||
#if FMT_HAS_GXX_CXX11
|
||||
EXPECT_EQ("(1, 2, 3)", format("({})", join(v1, ", ")));
|
||||
EXPECT_EQ("(+01.20, +03.40)", format("({:+06.2f})", join(v2, ", ")));
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -1644,3 +1684,10 @@ FMT_VARIADIC(void, custom_format, const char *)
|
||||
TEST(FormatTest, CustomArgFormatter) {
|
||||
custom_format("{}", 42);
|
||||
}
|
||||
|
||||
void convert(int);
|
||||
|
||||
// Check if there is no collision with convert function in the global namespace.
|
||||
TEST(FormatTest, ConvertCollision) {
|
||||
fmt::format("{}", 42);
|
||||
}
|
||||
|
||||
@@ -10090,8 +10090,9 @@ class FunctionMockerBase : public UntypedFunctionMockerBase {
|
||||
// threads concurrently.
|
||||
Result InvokeWith(const ArgumentTuple& args)
|
||||
GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
|
||||
return static_cast<const ResultHolder*>(
|
||||
this->UntypedInvokeWith(&args))->GetValueAndDelete();
|
||||
const ResultHolder *rh = static_cast<const ResultHolder*>(
|
||||
this->UntypedInvokeWith(&args));
|
||||
return rh ? rh->GetValueAndDelete() : Result();
|
||||
}
|
||||
|
||||
// Adds and returns a default action spec for this mock function.
|
||||
|
||||
@@ -320,7 +320,7 @@ TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||
|
||||
TEST(UtilTest, FormatSystemError) {
|
||||
fmt::MemoryWriter out;
|
||||
fmt::internal::format_system_error(out, EDOM, "test message");
|
||||
fmt::format_system_error(out, EDOM, "test message");
|
||||
EXPECT_EQ(out.str(), format_system_error(EDOM, "test message"));
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,6 @@ std::string read(File &f, std::size_t count) {
|
||||
|
||||
std::string format_system_error(int error_code, fmt::StringRef message) {
|
||||
fmt::MemoryWriter out;
|
||||
fmt::internal::format_system_error(out, error_code, message);
|
||||
fmt::format_system_error(out, error_code, message);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#define FMT_GTEST_EXTRA_H_
|
||||
|
||||
#include <string>
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
@@ -172,4 +172,10 @@ std::string read(fmt::File &f, std::size_t count);
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
|
||||
template <typename Mock>
|
||||
struct ScopedMock : testing::StrictMock<Mock> {
|
||||
ScopedMock() { Mock::instance = this; }
|
||||
~ScopedMock() { Mock::instance = 0; }
|
||||
};
|
||||
|
||||
#endif // FMT_GTEST_EXTRA_H_
|
||||
|
||||
@@ -2823,7 +2823,11 @@ inline int IsATTY(int /* fd */) { return 0; }
|
||||
inline int IsATTY(int fd) { return _isatty(fd); }
|
||||
# endif // GTEST_OS_WINDOWS_MOBILE
|
||||
inline int StrCaseCmp(const char* s1, const char* s2) {
|
||||
return _stricmp(s1, s2);
|
||||
# if _EMULATE_GLIBC
|
||||
return strcasecmp(s1, s2);
|
||||
# else
|
||||
return _stricmp(s1, s2);
|
||||
# endif
|
||||
}
|
||||
inline char* StrDup(const char* src) { return _strdup(src); }
|
||||
# endif // __BORLANDC__
|
||||
|
||||
@@ -36,8 +36,8 @@ class MockAllocator {
|
||||
MockAllocator() {}
|
||||
MockAllocator(const MockAllocator &) {}
|
||||
typedef T value_type;
|
||||
MOCK_METHOD1_T(allocate, T* (std::size_t n));
|
||||
MOCK_METHOD2_T(deallocate, void (T* p, std::size_t n));
|
||||
MOCK_METHOD2_T(allocate, T *(std::size_t n, const T *h));
|
||||
MOCK_METHOD2_T(deallocate, void (T *p, std::size_t n));
|
||||
};
|
||||
|
||||
template <typename Allocator>
|
||||
@@ -78,8 +78,10 @@ class AllocatorRef {
|
||||
|
||||
Allocator *get() const { return alloc_; }
|
||||
|
||||
value_type* allocate(std::size_t n) { return alloc_->allocate(n); }
|
||||
void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); }
|
||||
value_type *allocate(std::size_t n, const value_type *h) {
|
||||
return alloc_->allocate(n, h);
|
||||
}
|
||||
void deallocate(value_type *p, std::size_t n) { alloc_->deallocate(p, n); }
|
||||
};
|
||||
|
||||
#endif // FMT_MOCK_ALLOCATOR_H_
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmt/ostream.cc"
|
||||
#include "fmt/ostream.h"
|
||||
|
||||
#include <sstream>
|
||||
#include "gmock/gmock.h"
|
||||
@@ -35,13 +35,6 @@
|
||||
using fmt::format;
|
||||
using fmt::FormatError;
|
||||
|
||||
template <typename Char>
|
||||
std::basic_ostream<Char> &operator<<(
|
||||
std::basic_ostream<Char> &os, const BasicTestString<Char> &s) {
|
||||
os << s.value();
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const Date &d) {
|
||||
os << d.year() << '-' << d.month() << '-' << d.day();
|
||||
return os;
|
||||
@@ -128,22 +121,11 @@ TEST(OStreamTest, Print) {
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
}
|
||||
|
||||
TEST(OStreamTest, PrintfCustom) {
|
||||
EXPECT_EQ("abc", fmt::sprintf("%s", TestString("abc")));
|
||||
}
|
||||
|
||||
TEST(OStreamTest, FPrintf) {
|
||||
std::ostringstream os;
|
||||
int ret = fmt::fprintf(os, "Don't %s!", "panic");
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
EXPECT_EQ(12, ret);
|
||||
}
|
||||
|
||||
TEST(OStreamTest, WriteToOStream) {
|
||||
std::ostringstream os;
|
||||
fmt::MemoryWriter w;
|
||||
w << "foo";
|
||||
fmt::write(os, w);
|
||||
fmt::internal::write(os, w);
|
||||
EXPECT_EQ("foo", os.str());
|
||||
}
|
||||
|
||||
@@ -188,5 +170,5 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
|
||||
data += n;
|
||||
size -= static_cast<std::size_t>(n);
|
||||
} while (size != 0);
|
||||
fmt::write(os, w);
|
||||
fmt::internal::write(os, w);
|
||||
}
|
||||
|
||||
@@ -453,12 +453,6 @@ TEST(BufferedFileTest, FilenoNoRetry) {
|
||||
fileno_count = 0;
|
||||
}
|
||||
|
||||
template <typename Mock>
|
||||
struct ScopedMock : testing::StrictMock<Mock> {
|
||||
ScopedMock() { Mock::instance = this; }
|
||||
~ScopedMock() { Mock::instance = 0; }
|
||||
};
|
||||
|
||||
struct TestMock {
|
||||
static TestMock *instance;
|
||||
} *TestMock::instance;
|
||||
@@ -508,7 +502,7 @@ LocaleType newlocale(int category_mask, const char *locale, LocaleType base) {
|
||||
return LocaleMock::instance->newlocale(category_mask, locale, base);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__)
|
||||
typedef int FreeLocaleResult;
|
||||
#else
|
||||
typedef void FreeLocaleResult;
|
||||
|
||||
@@ -232,7 +232,7 @@ TEST(FileTest, MoveAssignmentClosesFile) {
|
||||
File OpenBufferedFile(int &fd) {
|
||||
File f = open_file();
|
||||
fd = f.descriptor();
|
||||
return std::move(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
TEST(FileTest, MoveFromTemporaryInCtor) {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
|
||||
#include "fmt/printf.h"
|
||||
#include "fmt/format.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
@@ -201,6 +202,8 @@ TEST(PrintfTest, HashFlag) {
|
||||
|
||||
TEST(PrintfTest, Width) {
|
||||
EXPECT_PRINTF(" abc", "%5s", "abc");
|
||||
EXPECT_PRINTF(" -42", "%5s", "-42");
|
||||
EXPECT_PRINTF(" 0.123456", "%10s", 0.123456);
|
||||
|
||||
// Width cannot be specified twice.
|
||||
EXPECT_THROW_MSG(fmt::sprintf("%5-5d", 42), FormatError,
|
||||
@@ -295,12 +298,13 @@ void TestLength(const char *length_spec, U value) {
|
||||
fmt::LongLong signed_value = 0;
|
||||
fmt::ULongLong unsigned_value = 0;
|
||||
// Apply integer promotion to the argument.
|
||||
fmt::ULongLong max = std::numeric_limits<U>::max();
|
||||
using fmt::internal::check;
|
||||
if (check(max <= static_cast<unsigned>(std::numeric_limits<int>::max()))) {
|
||||
using std::numeric_limits;
|
||||
fmt::ULongLong max = numeric_limits<U>::max();
|
||||
using fmt::internal::const_check;
|
||||
if (const_check(max <= static_cast<unsigned>(numeric_limits<int>::max()))) {
|
||||
signed_value = static_cast<int>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
} else if (check(max <= std::numeric_limits<unsigned>::max())) {
|
||||
} else if (const_check(max <= numeric_limits<unsigned>::max())) {
|
||||
signed_value = static_cast<unsigned>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
}
|
||||
@@ -379,11 +383,13 @@ TEST(PrintfTest, Bool) {
|
||||
TEST(PrintfTest, Int) {
|
||||
EXPECT_PRINTF("-42", "%d", -42);
|
||||
EXPECT_PRINTF("-42", "%i", -42);
|
||||
EXPECT_PRINTF("-42", "%s", -42);
|
||||
unsigned u = 0 - 42u;
|
||||
EXPECT_PRINTF(fmt::format("{}", u), "%u", -42);
|
||||
EXPECT_PRINTF(fmt::format("{:o}", u), "%o", -42);
|
||||
EXPECT_PRINTF(fmt::format("{:x}", u), "%x", -42);
|
||||
EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
|
||||
EXPECT_PRINTF(fmt::format("{}", u), "%s", u);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, LongLong) {
|
||||
@@ -395,7 +401,11 @@ TEST(PrintfTest, LongLong) {
|
||||
|
||||
TEST(PrintfTest, Float) {
|
||||
EXPECT_PRINTF("392.650000", "%f", 392.65);
|
||||
EXPECT_PRINTF("392.65", "%.2f", 392.65);
|
||||
EXPECT_PRINTF("392.6", "%.1f", 392.65);
|
||||
EXPECT_PRINTF("393", "%.f", 392.65);
|
||||
EXPECT_PRINTF("392.650000", "%F", 392.65);
|
||||
EXPECT_PRINTF("392.65", "%s", 392.65);
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%e", 392.65);
|
||||
EXPECT_PRINTF(buffer, "%e", 392.65);
|
||||
@@ -420,6 +430,7 @@ TEST(PrintfTest, Inf) {
|
||||
|
||||
TEST(PrintfTest, Char) {
|
||||
EXPECT_PRINTF("x", "%c", 'x');
|
||||
EXPECT_PRINTF("x", "%s", 'x');
|
||||
int max = std::numeric_limits<int>::max();
|
||||
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
||||
//EXPECT_PRINTF("x", "%lc", L'x');
|
||||
@@ -438,13 +449,17 @@ TEST(PrintfTest, Pointer) {
|
||||
int n;
|
||||
void *p = &n;
|
||||
EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
|
||||
EXPECT_PRINTF(fmt::format("{}", p), "%s", p);
|
||||
p = 0;
|
||||
EXPECT_PRINTF("(nil)", "%p", p);
|
||||
EXPECT_PRINTF(" (nil)", "%10p", p);
|
||||
EXPECT_PRINTF("(nil)", "%s", p);
|
||||
EXPECT_PRINTF(" (nil)", "%10s", p);
|
||||
const char *s = "test";
|
||||
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
|
||||
const char *null_str = 0;
|
||||
EXPECT_PRINTF("(nil)", "%p", null_str);
|
||||
EXPECT_PRINTF("(null)", "%s", null_str);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Location) {
|
||||
@@ -477,3 +492,20 @@ TEST(PrintfTest, PrintfError) {
|
||||
TEST(PrintfTest, WideString) {
|
||||
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, PrintfCustom) {
|
||||
EXPECT_EQ("abc", fmt::sprintf("%s", TestString("abc")));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, OStream) {
|
||||
std::ostringstream os;
|
||||
int ret = fmt::fprintf(os, "Don't %s!", "panic");
|
||||
EXPECT_EQ("Don't panic!", os.str());
|
||||
EXPECT_EQ(12, ret);
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Writer) {
|
||||
fmt::MemoryWriter writer;
|
||||
printf(writer, "%d", 42);
|
||||
EXPECT_EQ("42", writer.str());
|
||||
}
|
||||
|
||||
80
test/string-test.cc
Normal file
80
test/string-test.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Tests of string utilities
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
|
||||
#include "fmt/string.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using fmt::internal::StringBuffer;
|
||||
|
||||
TEST(StringBufferTest, Empty) {
|
||||
StringBuffer<char> buffer;
|
||||
EXPECT_EQ(0u, buffer.size());
|
||||
EXPECT_EQ(0u, buffer.capacity());
|
||||
std::string data;
|
||||
// std::string may have initial capacity.
|
||||
std::size_t capacity = data.capacity();
|
||||
buffer.move_to(data);
|
||||
EXPECT_EQ("", data);
|
||||
EXPECT_EQ(capacity, data.capacity());
|
||||
}
|
||||
|
||||
TEST(StringBufferTest, Reserve) {
|
||||
StringBuffer<char> buffer;
|
||||
std::size_t capacity = std::string().capacity() + 10;
|
||||
buffer.reserve(capacity);
|
||||
EXPECT_EQ(0u, buffer.size());
|
||||
EXPECT_EQ(capacity, buffer.capacity());
|
||||
std::string data;
|
||||
buffer.move_to(data);
|
||||
EXPECT_EQ("", data);
|
||||
}
|
||||
|
||||
TEST(StringBufferTest, Resize) {
|
||||
StringBuffer<char> buffer;
|
||||
std::size_t size = std::string().capacity() + 10;
|
||||
buffer.resize(size);
|
||||
EXPECT_EQ(size, buffer.size());
|
||||
EXPECT_EQ(size, buffer.capacity());
|
||||
std::string data;
|
||||
buffer.move_to(data);
|
||||
EXPECT_EQ(size, data.size());
|
||||
}
|
||||
|
||||
TEST(StringBufferTest, MoveTo) {
|
||||
StringBuffer<char> buffer;
|
||||
std::size_t size = std::string().capacity() + 10;
|
||||
buffer.resize(size);
|
||||
const char *p = &buffer[0];
|
||||
std::string data;
|
||||
buffer.move_to(data);
|
||||
EXPECT_EQ(p, &data[0]);
|
||||
EXPECT_EQ(0u, buffer.size());
|
||||
EXPECT_EQ(0u, buffer.capacity());
|
||||
}
|
||||
|
||||
TEST(StringWriterTest, MoveTo) {
|
||||
fmt::StringWriter out;
|
||||
out << "The answer is " << 42 << "\n";
|
||||
std::string s;
|
||||
out.move_to(s);
|
||||
EXPECT_EQ("The answer is 42\n", s);
|
||||
EXPECT_EQ(0u, out.size());
|
||||
}
|
||||
|
||||
TEST(StringWriterTest, WString) {
|
||||
fmt::WStringWriter out;
|
||||
out << "The answer is " << 42 << "\n";
|
||||
std::wstring s;
|
||||
out.move_to(s);
|
||||
EXPECT_EQ(L"The answer is 42\n", s);
|
||||
}
|
||||
|
||||
TEST(StringTest, ToString) {
|
||||
EXPECT_EQ("42", fmt::to_string(42));
|
||||
}
|
||||
60
test/time-test.cc
Normal file
60
test/time-test.cc
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Time formatting tests
|
||||
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
All rights reserved.
|
||||
|
||||
For the license information refer to format.h.
|
||||
*/
|
||||
#ifdef WIN32
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "fmt/time.h"
|
||||
|
||||
TEST(TimeTest, Format) {
|
||||
std::tm tm = std::tm();
|
||||
tm.tm_year = 116;
|
||||
tm.tm_mon = 3;
|
||||
tm.tm_mday = 25;
|
||||
EXPECT_EQ("The date is 2016-04-25.",
|
||||
fmt::format("The date is {:%Y-%m-%d}.", tm));
|
||||
}
|
||||
|
||||
TEST(TimeTest, GrowBuffer) {
|
||||
std::string s = "{:";
|
||||
for (int i = 0; i < 30; ++i)
|
||||
s += "%c";
|
||||
s += "}\n";
|
||||
std::time_t t = std::time(0);
|
||||
fmt::format(s, *std::localtime(&t));
|
||||
}
|
||||
|
||||
TEST(TimeTest, EmptyResult) {
|
||||
EXPECT_EQ("", fmt::format("{}", std::tm()));
|
||||
}
|
||||
|
||||
bool EqualTime(const std::tm &lhs, const std::tm &rhs) {
|
||||
return lhs.tm_sec == rhs.tm_sec &&
|
||||
lhs.tm_min == rhs.tm_min &&
|
||||
lhs.tm_hour == rhs.tm_hour &&
|
||||
lhs.tm_mday == rhs.tm_mday &&
|
||||
lhs.tm_mon == rhs.tm_mon &&
|
||||
lhs.tm_year == rhs.tm_year &&
|
||||
lhs.tm_wday == rhs.tm_wday &&
|
||||
lhs.tm_yday == rhs.tm_yday &&
|
||||
lhs.tm_isdst == rhs.tm_isdst;
|
||||
}
|
||||
|
||||
TEST(TimeTest, LocalTime) {
|
||||
std::time_t t = std::time(0);
|
||||
std::tm tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(EqualTime(tm, fmt::localtime(t)));
|
||||
}
|
||||
|
||||
TEST(TimeTest, GMTime) {
|
||||
std::time_t t = std::time(0);
|
||||
std::tm tm = *std::gmtime(&t);
|
||||
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t)));
|
||||
}
|
||||
@@ -64,7 +64,7 @@ namespace {
|
||||
struct Test {};
|
||||
|
||||
template <typename Char>
|
||||
void format(fmt::BasicFormatter<Char> &f, const Char *, Test) {
|
||||
void format_arg(fmt::BasicFormatter<Char> &f, const Char *, Test) {
|
||||
f.writer() << "test";
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ void CheckForwarding(
|
||||
// Check if value_type is properly defined.
|
||||
AllocatorRef< MockAllocator<int> >::value_type *ptr = &mem;
|
||||
// Check forwarding.
|
||||
EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr));
|
||||
ref.allocate(42);
|
||||
EXPECT_CALL(alloc, allocate(42, 0)).WillOnce(Return(ptr));
|
||||
ref.allocate(42, 0);
|
||||
EXPECT_CALL(alloc, deallocate(ptr, 42));
|
||||
ref.deallocate(ptr, 42);
|
||||
}
|
||||
@@ -339,7 +339,7 @@ TEST(MemoryBufferTest, Grow) {
|
||||
EXPECT_EQ(10u, buffer.capacity());
|
||||
int mem[20];
|
||||
mem[7] = 0xdead;
|
||||
EXPECT_CALL(alloc, allocate(20)).WillOnce(Return(mem));
|
||||
EXPECT_CALL(alloc, allocate(20, 0)).WillOnce(Return(mem));
|
||||
buffer.grow(20);
|
||||
EXPECT_EQ(20u, buffer.capacity());
|
||||
// Check if size elements have been copied
|
||||
@@ -360,7 +360,7 @@ TEST(MemoryBufferTest, Allocator) {
|
||||
MemoryBuffer<char, 10, TestAllocator> buffer2((TestAllocator(&alloc)));
|
||||
EXPECT_EQ(&alloc, buffer2.get_allocator().get());
|
||||
std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE;
|
||||
EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem));
|
||||
EXPECT_CALL(alloc, allocate(size, 0)).WillOnce(Return(&mem));
|
||||
buffer2.reserve(size);
|
||||
EXPECT_CALL(alloc, deallocate(&mem, size));
|
||||
}
|
||||
@@ -373,13 +373,13 @@ TEST(MemoryBufferTest, ExceptionInDeallocate) {
|
||||
std::size_t size = 2 * fmt::internal::INLINE_BUFFER_SIZE;
|
||||
std::vector<char> mem(size);
|
||||
{
|
||||
EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0]));
|
||||
EXPECT_CALL(alloc, allocate(size, 0)).WillOnce(Return(&mem[0]));
|
||||
buffer.resize(size);
|
||||
std::fill(&buffer[0], &buffer[0] + size, 'x');
|
||||
}
|
||||
std::vector<char> mem2(2 * size);
|
||||
{
|
||||
EXPECT_CALL(alloc, allocate(2 * size)).WillOnce(Return(&mem2[0]));
|
||||
EXPECT_CALL(alloc, allocate(2 * size, 0)).WillOnce(Return(&mem2[0]));
|
||||
std::exception e;
|
||||
EXPECT_CALL(alloc, deallocate(&mem[0], size)).WillOnce(testing::Throw(e));
|
||||
EXPECT_THROW(buffer.reserve(2 * size), std::exception);
|
||||
@@ -581,7 +581,7 @@ struct CustomFormatter {
|
||||
typedef char Char;
|
||||
};
|
||||
|
||||
void format(CustomFormatter &, const char *&s, const Test &) {
|
||||
void format_arg(CustomFormatter &, const char *&s, const Test &) {
|
||||
s = "custom_format";
|
||||
}
|
||||
|
||||
@@ -708,7 +708,7 @@ TEST(ArgVisitorTest, VisitUnhandledArg) {
|
||||
|
||||
TEST(ArgVisitorTest, VisitInvalidArg) {
|
||||
Arg arg = Arg();
|
||||
arg.type = static_cast<Arg::Type>(Arg::CUSTOM + 1);
|
||||
arg.type = static_cast<Arg::Type>(Arg::NONE);
|
||||
EXPECT_ASSERT(TestVisitor().visit(arg), "invalid argument type");
|
||||
}
|
||||
|
||||
@@ -834,10 +834,10 @@ void check_throw_error(int error_code, FormatErrorMessage format) {
|
||||
|
||||
TEST(UtilTest, FormatSystemError) {
|
||||
fmt::MemoryWriter message;
|
||||
fmt::internal::format_system_error(message, EDOM, "test");
|
||||
fmt::format_system_error(message, EDOM, "test");
|
||||
EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), message.str());
|
||||
message.clear();
|
||||
fmt::internal::format_system_error(
|
||||
fmt::format_system_error(
|
||||
message, EDOM, fmt::StringRef(0, std::numeric_limits<size_t>::max()));
|
||||
EXPECT_EQ(fmt::format("error {}", EDOM), message.str());
|
||||
}
|
||||
@@ -846,12 +846,12 @@ TEST(UtilTest, SystemError) {
|
||||
fmt::SystemError e(EDOM, "test");
|
||||
EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), e.what());
|
||||
EXPECT_EQ(EDOM, e.error_code());
|
||||
check_throw_error<fmt::SystemError>(EDOM, fmt::internal::format_system_error);
|
||||
check_throw_error<fmt::SystemError>(EDOM, fmt::format_system_error);
|
||||
}
|
||||
|
||||
TEST(UtilTest, ReportSystemError) {
|
||||
fmt::MemoryWriter out;
|
||||
fmt::internal::format_system_error(out, EDOM, "test error");
|
||||
fmt::format_system_error(out, EDOM, "test error");
|
||||
out << '\n';
|
||||
EXPECT_WRITE(stderr, fmt::report_system_error(EDOM, "test error"), out.str());
|
||||
}
|
||||
@@ -956,3 +956,17 @@ TEST(UtilTest, Conditional) {
|
||||
fmt::internal::Conditional<false, int, char>::type *pc = &c;
|
||||
(void)pc;
|
||||
}
|
||||
|
||||
struct TestLConv {
|
||||
char *thousands_sep;
|
||||
};
|
||||
|
||||
struct EmptyLConv {};
|
||||
|
||||
TEST(UtilTest, ThousandsSep) {
|
||||
char foo[] = "foo";
|
||||
TestLConv lc = {foo};
|
||||
EXPECT_EQ("foo", fmt::internal::thousands_sep(&lc).to_string());
|
||||
EmptyLConv empty_lc;
|
||||
EXPECT_EQ("", fmt::internal::thousands_sep(&empty_lc));
|
||||
}
|
||||
|
||||
@@ -87,6 +87,13 @@ const Char BasicTestString<Char>::EMPTY[] = {0};
|
||||
typedef BasicTestString<char> TestString;
|
||||
typedef BasicTestString<wchar_t> TestWString;
|
||||
|
||||
template <typename Char>
|
||||
std::basic_ostream<Char> &operator<<(
|
||||
std::basic_ostream<Char> &os, const BasicTestString<Char> &s) {
|
||||
os << s.value();
|
||||
return os;
|
||||
}
|
||||
|
||||
class Date {
|
||||
int year_, month_, day_;
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user