Squashed 'externals/fmt/' changes from c4ee72653..f5e54359d
f5e54359d Update version a003ab93c Update changelog 9b7416081 `atomic_flag` formatting (#3594) a21690bdf Coding conventions c9efd8968 Merge branch 'MathewBensonCode-master' e78682405 Refactor Improvement on umul128 function 2b20d7be6 Refactor To remove friend function private access 1f38ebbdb Add condition to include friend function in win32 e41817969 Fix for FMT_MODULE not compiling on GCC 0bffed895 Bump actions/checkout from 3.1.0 to 3.5.3 (#3602) cc077a5e3 Fix `FMT_NO_UNIQUE_ADDRESS` warning with clang-cl. (#3600) a992b3d1f Fix for Compilation Error When Using FMT_MODULE (#3597) af1b768cc Update changelog 2a7c45b54 Update changelog bd868f3a5 Improve compatibility with GBK #3598 (#3599) bbb784fb1 Fix a gcc error about partial specialization after instantiation 6c845f57e Fix `formatted_size` with `FMT_COMPILE` and format specs (#3588) a379595c5 Bump actions/upload-artifact from 3.1.0 to 3.1.2 (#3593) 9dfde7871 Bump ossf/scorecard-action from 2.1.2 to 2.2.0 (#3592) e92a705bd Bump github/codeql-action from 2.2.4 to 2.21.4 (#3591) 5a866fe85 Add formatter for std::atomic (#3574) e150ea0cc to_string supports types with format_as 29ce2ff8a Update README.rst 2e1362add Create scorecard.yml e57ca2e36 Update version 4c56612c6 Update changelog e1acd5f4d Bump version 6ffee2f75 Handle new rst nodes ee475d640 Update changelog ecc914125 Update changelog d42486231 Fix fixed precision handling during rounding in long double 3c1b3337d Update changelog 35fb69ebe Update changelog f92be35c0 Update changelog 7a2f6ac21 Fix a comment 744ff5554 Fix docs 70ae48b00 Update changelog ebb10347c Merge branch 'master' of github.com:fmtlib/fmt 100572016 Clarify that visit_format_arg is deprecated aeb6ad4dd Added formatter for bit_reference-like types (#3570) 96d1fa22d Update changelog 8a4bec5cf fix ambiguous formatter lookup for flat_set (#3561) eacd51c24 cmake: fix FMT_PKGCONFIG_DIR path (#3563) 757564f5c add missing inline specifier (#3552) f4214ae8d Use the U literal for unsigned integer constants. (#3549) aecf80d30 Update a comment 503d49286 expose detail::throw_format_error (#3551) 4f46cb82f Remove std::copy usage. (#3550) 3dec65b7f Use replacement character in path dbabb305c Turn off error-producing NVCC workaround when using c++20 (#3544) ac0ab8eff Improve path formatter 40f35d6f0 Cleanup std formatters 31c2c5679 Cleanup formatters b2728a317 Improve path formatter 77e0b0e22 Cleanup chrono formatter e47585904 Fix overspecified tests 436c131d4 Optimize compiled format_to_n 388bc296b Fix fixed formatting of small long doubles 95e1ea573 Clarify requirement on compiled formatter fb97cb231 Suppress a bogus stringop-overflow warning dd5a9691f Clarify that data is not null-terminated 72dc4491e Fix format_string_checker initialisation order (#3542) 9bea6ec04 Don't use deprecated checked_array_iterator 661b23ede Call parse on empty specs at compile time 8e87d3a8b Fix include a47491656 Update CMakeLists.txt (#3523) de4705f84 Trying to improve errors in the unformattable case (#3478) e4c8cfe38 README: Add link to merged clang-tidy check (#3515) 606f85f8b Workaround brain-damaged conversions a331dbfb6 Fix type in assert message. (#3508) 13156e54b Revert "add ability to build Apple framework using CMAKE_FRAMEWORK" (#3496) 9158bea1e Remove old grisu tests dd17f89a1 Fix for issue #3492 (#3493) 1daae555b Optimize format string compilation 6ad301235 Optimize code unit output in compiled format 8732ad877 Update docs 5afb1821a Update docs a81135f2c Fix a link dfc34821a Update docs 0765e7284 Update docs 977d887a4 Suppress a bogus warning c86fe0b8d Give basic_memory_buffer allocator [[no_unique_address]] (#3485) 5dbe0ff95 Bump actions/checkout from 3.5.2 to 3.5.3 (#3486) de0757b57 Use FMT_TRY and FMT_CATCH in std.h (#3482) 8fe893c0a Update README.rst 0f823df46 Remove detail namespace hack 60fd9941c Use correct Char type in std::filesystem::path (#3476) 8abfc145b Fix MSVC warning in std::chrono::time_point formatter (#3475) 686b3353a Simplify typeid check b2106f363 Tweak comments 35547d600 Use const_check to silence MSVC warning 179c7e5a6 Use typeid() only if it's available 61fb3a15f Add VxWorks7 user space and kernel space support (#3467) bd393456e Remove FMT_*_DETAIL_NAMESPACE 6be36af0d Remove udl_formatter 2a35eeed8 Remove unused data 256a826d6 Consolidate formatters 6369af37d Simplify formatters 0b8404918 Remove get_cached_power 171a020c8 Pass correct Char to base format_as formatter (#3457) d8f04e399 Simplify ceil 5e988f8df Remove is_constant_evaluated() check 19b17618a Make constexpr ceil c684a06d5 New CI: macOS 13, Xcode 14.3, C++ 20 abdb7fdf8 Update api.rst 8b09fe2a0 Fix example for user-defined types in documentation for 10.0.0 (#3461) adad18a74 Update ChangeLog.rst 858e528ab Use dragon in constexpr a54cb108d CMake: Do not fail on unknown compiler features (#3453) ef55d4f52 Suppress a bogus gcc warning 70b6a6fa4 Update README.rst (#3454) 6fe895410 Remove invalid_arg_index d0652d225 Create dependabot.yml (#3452) de8d0171a Simplify FMT_FORMAT_AS 7401fe046 Hash pin Github Workflows (#3451) 08ef0d084 fix formatter<char*> (#3432) d60b907f8 Replace mod_inv_25 by explicit value (#3450) 4ce086f73 remove code duplication (#3448) 821f8cdb4 Detemplatize printf more 0bf6ed7e1 Cleanup printf e40e04fac Detemplatize printf_arg_formatter 0a1c27281 Detemplatize basic_printf_context 2f605cc89 Deprecate wide printf 1d54499ac Apply coding conventions 3c6053c53 Cleanup basic_printf_context 4a392adaa Remove basic_printf_parse_context b14913fae FMT_MODULE_EXPORT -> FMT_EXPORT 2117df299 format-inl.h: address implicit int to bool conversion (#3446) 616a49378 Revert Char* formatter removal 9a034b0d5 Always assert in FMT_THROW (#3439) e0fc0e85e Remove unneeded FMT_API 552c43aba Improve long formatters d6846f4ac Clarify why byte formatter is in core aeedac588 Remove unneeded specialization eaa630769 Make hex float test more stable (#3434) e82bf41a1 Update README.rst e077396f5 Update docs 861facad0 Fix a typo 75bfe5761 Improve docs 697e76ba3 Don't call init_named_args unnecessarily a425e0ff3 Cleanup xchar API c36dd825b Remove unnecessary forwarding 130b8fcdb Reduce template instantiations a47e8419b Cleanup the core API ea49c91cd Cleanup argument construction d7592ad8b Fix time_point formatting for durations with certain ratios (#3430) ebfb2e677 Remove unused alias 5780269d5 Improve API safety b47119216 Fix a comment 8f18e72df Improve API safety 93d7cb12f Fix formatting 0e4278717 Remove unused macro 93a30a074 unicode_to_utf8 -> to_utf8 since both sides of conversion are Unicode a08196b14 Update ChangeLog.rst 0398ba42c Update ChangeLog.rst a0b8a92e3 Update version 5cf2342aa Bump version fe9d39d7c Update changelog 4c9856197 Update changelog 403b271ed Update changelog 2c991e1af Update changelog c984df981 Remove an unused function from internal class fbf21ed22 Update changelog 575583144 Update changelog e7f6888c7 Update changelog 39db2dfd0 Update changelog 9b7829e26 Update changelog 1e0ce567e Fix formatting of paths containing invalid Unicode dde8cf3bb Unification utf16/utf32 to utf8 conversion e84b00e01 Workaround to error: variable 'n' set but not used [-Werror,-Wunused-but-set-variable] b12ffea4f Add filesystem_error test back f61f15cc5 Suppress a false positive in gcc 192df93d7 modules missing pieces (#3399) d8973bf16 Add FMT_STRING for format_to() call (#3413) d7a8e50cb Improve module testing (#3397) 02cae7e48 Improve handling of Unicode in paths 53162142b Remove .bazelrc mention from Bazel related readme (#3411) 5bcf0d7f9 Bazel support (#3406) f8c9fabd9 Fix spelling (#3404) 62ff4e1db Remove foonathan from maintainer list (#3402) f449ca052 Name `vfprintf` clashes with the identically named declaration in 'stdio.h' if that happens to be #included into the same TU. Fix this by using qualified name lookup instead of unqualified lookup that also enables ADL. (#3400) eafcd3c8e Optionally attach declarations to the `global module` rather than `module fmt` (#3387) 18154cc90 Simplify print 0de789cf2 Update changelog c03938922 export names only once (#3392) 93e81bb5d Fix C4365 (signed/unsigned mismatch) warning on 32-bit Windows (#3398) e7d6eb679 Update tests to use recommended MOCK_METHOD (#3395) 18e7a2532 Remove obsolete msvc workarounds (#3388) 0489c19dc fix and improve module (#3386) 8ec94ac6a Use full path to pcm d97d8cea6 Push module check to test d8a2698e6 Fix compilation as a C++20 module with gcc 13 d9c19940a Update add_module_library 4b5ae0b0e Remove unnecessary module support check 75f3b1c09 Use add_module_library faf83406a Workaround cmake issue 165814d57 Add module support to CMake 33f715077 Fix error C2668 on msvc (#3378) c98e5a08a Fix modular build on clang 119c6bd16 Move the modules check 77eeb7183 Remove unused headers 13bf99f9d Enable modules in clang 16 1d0257e4c FMT_MODULE_EXPORT_* -> FMT_EXPORT_* 4613d48fd FMT_EXPORT -> FMT_LIB_EXPORT 4a4a2a2bd Fix diagnostics fce74caa1 Disable problematic implicit conversions 02bf4d1c1 Disable to_string_view ADL 466e0650e Remove problematic workaround 029caa8ea Update changelog e406ddbfa Remove broken part of the config 909567953 Update changelog 7f46cb75b ranges: Fix extra semi (#3374) 4e3f38105 Update changelog d3c10f516 fix compilation for MSDOS (#3369) ab956f600 Update changelog 97aedeab4 Workaround a double-double hexfloat format (#3366) bce8d4ed0 Remove stray comment a91c7b286 Cleanup the core API 19c074e47 Remove deprecated fallback formatter 41cfc739f Generalize format_as f6276a2c2 Force use a signed char (On ARM char is unsigned by default) (#3362) 6002ddf82 Remove a deprecated option 6549ffde8 Improve format_as safety d9bc5f132 Fix code causing spurious Wstringop-overflow warning 9c5cd998d Remove unused functions 93bfa0538 %T is %H:%M:%S (#3349) d8e1c4265 fix case of variant which is valueless by exception (#3347) e1720c0e5 Fix CUDA nvcc warning fmt/include/fmt/core.h(295): warning #1675-D: unrecognized GCC pragma (#3352) 7f882918e `write_floating_seconds`: Fall back to `::round` (#3343) cbc7b8d5c Cleanup dead variable (#3338) 050293646 Path is not escaped twice in the debug mode (#3321) 3daf33837 Enable consteval for Xcode 14.0.1 and later (#3331) e0748e61d Fix recursion check in range formatting b94e1016f chore: set permission to cifuzz.yml (#3328) 98699719f Make # handling consistent with std::format 48dfbcaa9 Improve license and PR template wording c644c753d Add '🐙 userver framework' to the projects 73b7cee7f Fix for issue #3325 (#3326) 5b8302079 Add optional support (#3303) 3a69529e8 Fix various typos (#3312) 76f520835 Call element parse in tuple parse 507c3042d class -> typename 1741e90de Always call parse in range formatter d646fd0da Minor cleanup b5c2f74f4 change sopen_s to wsopen_s (fmtlib#3234) (#3293) e03753c4a Add ubuntu mirrors (#3302) 6e6eb6377 [msvc] fix warning about non-inline variable 3c5464ba1 Fix OpenBSD build error (#3295) 655046d24 Fix container adaptor formatting 581c6292c Add formatters for container adapters (#3279) 7718eeeac Implement glibc ext for sec, min, and hour (#3271) 44e0eea94 Use FMT_HAS_INCLUDE and apply clang-format 99070899b Fix errors setting of FMT_USE_FLOAT128 (#3259) 05e3a9233 Allowing formatting non-copyable ranges. (#3290) 70db193f0 Visual Studio 2022: fmt/format-inl.h(1145,60): warning C4310: cast truncates constant value #3287 (#3288) a2c05a10e Workaround a bug in MSVC <= 19.22 cae9bf45b Simplify apidoc comments 87c066a35 Implement `println` (#3267) 9409b2e4d Workaround for incompatibility between libstdc++ consteval-based std::is_constant_evaluated() implementation and clang-14 (#3281) f89cd276f Refactor countl_zero fallback (#3276) 240b728d8 Report an error on overflow dfbb952b2 Fix empty spec for time point (#3275) 39971eb33 Fix localized format for float-point numbers (#3272) 0f42c17d8 Implement a new formatting algorithm for small given precision (#3269) bfc0924ea Bugfix for fmt::printf on Power9 architecture with the XL compiler (#3256) 676c2a107 Fix negative subsec for time_point (#3261) 2c80cedc3 Fix standard default installation target presence (#3264) dda53082b Support fill, align & width for time point (#3260) 2622cd23e Simplify arg_mapper 9e4a54fa6 Disable remaining implicit conversions 9ce648067 Disble deprecated implicit enum conversions 9121f9b1d Enable format_as for classes b7535365b Enable format_as for non-integral types 09ed1ddb9 Cleanup tests 0ec65d99a Merge parse_presentation_type into parse_format_specs 71e4e0272 Cleanup vprintf aad546baa Simplify presentation type parsing 14a69fcc5 Use parse_align bf34ffd33 Refactor format string parsing 6056e0712 Simplify symbols aa99b8640 Minor cleanup 6ade2eb4e Inline all_int_set caa697494 Simplify parse functions a73a9b6a8 Refactor format string checks 72785a3ab Cleanup write 0c3dd5ddd Remove redundant check 739b600f4 Remove iterator shenanigans 3710c4d38 Link to dragonbox a05ba44df Simplify format string parsing ffb9b1d13 Improve handling of signed types 32190859e Fix handling of char 8fe4d97d5 Reduce template instantiations 7e5a95956 Fix build with MSVC C++20 modules (#3254) 9e6030486 Clarify that unused args are allowed 7ad48c1f6 Cleanup core.h a921a596e Cleanup core.h 3e762fdf5 Use ignore_unused 79981a252 Cleanup ranges formatting bd12aaa98 Simplify format string parsing b8f36207c Simplify format string parsing d907786f0 Move anchor to where it belongs f2355bbe5 Fix docs f398c9476 Fix docs 4841784e8 Simplify C99 strftime detection conditions cb72c23e9 Improve timezone tests 583f2d820 Set timezone for chrono tests 32c439070 Minor cleanup 3a5e19fbf Minor cleanup dfb857ebe Refactor format spec parsing 9ea9b6bcb Cleanup arg id parsing 2b0ff62a7 Remove unused template arg from format_string_checker d1745084e Simplify parse context 407e7b7b6 basic_format_specs -> format_specs 3cf979475 Cleanup format string parsing 934c8e5f7 Refactor precision parsing fc9693834 Remove empty semicolon f0ab112c3 Cleanup parsing 9660e5b95 Remove redundant tests a585571e9 Ignore 0 character with align 840ec8569 Cleanup width handlers 1dadeb8a3 Refactor width parsing 275b4b341 Remove obsolete parse-benchmark e004f1d69 Fix for issue #3241 bde1a6070 Simplify fill and alignment parsing 040dc2a5d small typo in syntax.rst 6a186bcd6 Localize FMT_USE_LOCAL_TIME 8c56919bd Check chrono spec starts with % 115001a3b Formatting of system clocks ought to be to UTC, not to local time. This improves standards conformance of fmt. b90895412 Fix formatting of named arguments with locale d072f1dc6 Fix for issue #3228 3999fd193 Workaround an ADL issue c06e0b4ed Extract timezone offset from timezone conversion functions 1bf302a4e Implement %Ez, %Oz for chrono formatter f1733afd4 Pin godbolt example to specific version f61dcccc6 Update README.rst f9bcbdcbc Update README.rst 1a854b4aa Clarify what mod_inv_5 is 62ceb181b fix #3105 - Compile-time error when mixing named argument with automatic indexing b0c8263cb include/fmt/core.h: copy constructors removal d24be2e95 Add countl_zero function 8d50d814d Fix a chrono formatting issue found by fuzzing 115ca96e0 Bump tested CMake version to 3.25 886491625 Remove workaround for GTest bug 74c51ff37 Skip only strptime dependent test in scan-test 69ffedfe5 Use `target_compile_features` to specify C++ standard requirement fae6f7e08 Optimize range formatter a69e43c9d Update benchmark results 91c024ed3 Rename leading_v -> leading_xdigit 649aa102d CI linux: add CIFuzz Github action 31364732d Replace snprintf-based hex float formatter with internal implementation 74d55a493 Add missing operators 8276f1a20 Fix warning: the implicit by-copy capture of "this" is deprecated (EDG frontend) 81ebe70b9 Fix warning: a class type that is not trivially copyable passed through ellipsis (EDG frontend) 3160847eb Enable C++17 tests on macOS 6a95f8c7e Add missing env CTEST_OUTPUT_ON_FAILURE c7980542d Skip the scan-test if strptime isn't defined (#3184) 7df30f91a Format unique_ptr with custom deleter (#3177) d2e89c8b0 Document more chrono specs bd1959320 Document more chrono specs fd0d0ec8d Document more chrono specs 8e93434ed Update README.rst fc07217d8 Make utf-8 detection compatible with gbk cb7373b46 Replace format with FMT_STRING. (#3162) 795ed8abf Add precision modifier for seconds in chrono format (#3148) 66d71a1b3 Fixing formatting of range of range of char. (#3158) 80f8d3442 fmt::ostream - aggregate buffer instead of inheriting it (#3139) 64965bdc9 Add locale getter in tm_writer (#3147) e1ffa7655 Fix warning: conditional expression is constant. (#3150) 8c19bf3f2 Mention MariaDB amongst the projects that use fmt (#3145) f67dbc981 Remove duplicate implementation (#3144) cd7202e03 Fix overflow error (#3143) 51d3685ef Remove duplicate template parameter. (#3142) 9254cfa6f Support formatting of subseconds (#3115) cfb34a060 Avoid using `uint` as a type name (#3137) 5ad7b7138 Fix options for C++20 experimental module in CMake (#3134) d2c47c0df Fix broken condition (#3129) 491c32cbd Workaround gcc bug 103879 662adf4f3 Move formatter<std::error_code> from fmt/os.h to fmt/std.h (#3125) ad91cab37 Normalization of stdlib inline namespace names (#3119) 0ccaed3a6 Set CMAKE_RUNTIME_OUTPUT_DIRECTORY relative to CMAKE_CURRENT_BINARY_DIR instead of CMAKE_BINARY_DIR (#3120) ad719619c Support formatting of std time_point with utc_clock (#3110) 6e0a5f7fb Update CI to Ubuntu 20.04 and to newer versions of actions 48f525d02 Add basic_format_string::get() 0b5cb18b7 Use buffering in to_string to avoid bloat 4c4f99a58 Update a godbolt link 3272a7a3c Update an example a48e3355a Improve docs afcf42429 Update docs ac85afaab Simplify format_error 3178bb9a2 Update docs cf58f64c5 Update docs e4e0ae391 Use fmt/core.h in examples d65acc4e6 Improve docs c9f790b06 Update docs 6b8144a5a Update locale docs 2d66ad5d3 Suppress -Wshadow 042af5332 Suppress -Wfloat-equal 192859c2b Optimize writing to buffers via back_insert_iterator e2f6d7665 fix gcc <= 7.1 compile errors 61844b6b6 Fix build error on GCC-9 7a752e75f New CI: GCC-9 94ceb38a0 Improve locale API 58c4c012f Disable slow windows build and simplify write_loc c3494ae36 Refactor float localization 8ae56161c Fix compilation with FMT_ENFORCE_COMPILE_STRING and FMT_WERROR (#3091) 76705fc2e Update doc 21c2137e7 Add class name output to formatter for std::exception (#3076) ecffca672 Don't parse '}' as fill 3176e0fad Add locale overload for formatted_size (#3084) (#3087) 1feb430fa Fix intellisense on Windows (#3082) b98ffb7db Improve locale handling bac53951b Add starts_with to basic_string_view. (#3080) d59b89e9c More locale 58a5563a9 Implement grouping 1b94271ff Add support for UTF-8 digit separators 768d79a83 Implement format_facet 91ecb38a3 Localize negative integers aec3bb5d0 Workaround C complex.h idiocy 29c600013 Simplify is_variant_like_ check, fix compile error before GCC 11 (#3072) fec5515c5 num_format_facet -> format_facet f187274d3 Add loc_value fc5e59fe4 Don't use stringstream d6a870460 Improve locale support 56c72a671 Reduce locale dependency 4191477b9 Add formatter for std::exception (#3062) 75383a87f Inline trivial functions 48327a82e Make format.h compile faster b79ed4105 Remove unnecessary type_identity 64e29893c Improve locale support 0b0f7cfbf hip workaround 40e414d82 Fix compilation error with gcc-7.2.0 33b4c33c5 Requires FMT_CONSTEXPR20 support a07411c2b Disable compile-time checks for dynamic width/precision test for LCC and compiler without std::is_constant_evaluated() 797d82b21 Disable non-type template args for LCC a553521d6 Disable "GCC optimize" pragma for LCC a33701196 Update version 1f575fd5c Bump version c7635288f Fix docs, take 2 c8ed78e31 Fix docs e07cfb206 Update changelog 1dc7af569 Fix markup f7d21c3a1 Update changelog a55bcb24b Update changelog 30cb2b312 Remove appveyor config cf8d3c322 Update changelog 3c3cb6f6b Update changelog 91481f255 Detemplatize code_point_length_impl f98048b62 Fix bugs in utf8 decoder (#3056) 4a8e2949b Fix formatting of ranges of code unit types 3a3b0709e Disable bogus -Wstringop-overflow on GCC 11 (#3054) e724bbea1 Fix wchar_t corner cases 665d9779e Disable non-type template args for nvhpc (#3053) 13d07c6a3 Apply doc fixes retroactively 391f922ac Improve error reporting in format string compilation dc59d3df3 Fix a warning in gtest 489dabbd3 Fix decoder on broken utf8 sequences. (#3044) 541cd2183 Fix locale name (thanks Mikhail Paulyshka) 1f95c3438 Fix sign handling with large code units 779449fd9 Belarusify test fbb568bce nvhpc/22.3: workaround for c++17 mode. (#3043) 36c23bd5f Prepare for deprecating FindPythonInterp module. (#3040) 9ff0f3a7d Fix docs fd41110d3 Add MinGW to CI fc23cfbf4 Fix testsuite on MinGW + MSVCRT fd93b633b Constexpr formatted_size (#3026) 7fb8d33f9 Fix compile-time width/precision type check 8bd02e93b Reduce conditional compilation d9c1c7353 Use is_utf8() in print(std::ostream&, ...) 682e097be Remove -Wl,--as-needed linker option b9087ee58 Suppress unused typedef warning df56fdf88 Clarify docs for compile-time checks 90c48b852 Remove double code execution 5a8b7cd74 Add comment 36a25d75b Reuse detail::string_literal in the chrono formatter 6c9304b2c Replace ParseContext with basic_format_parse_context 24ab9dd19 Remove duplicate method a95dc1701 Remove unused condition 5f774c0ae I shouldn't be there 6567df7f2 Update README.rst 6c6b1fbf6 Workaround a bug in MSVC 9beddd08f Improve CI on Windows 6452e3c9e Fix a typo in example 756822ba3 Fix Unicode handling for ostream under Windows with libc++. (#3001) 0b2862a1e Range formatter (#2983) 258000064 Add fmt:: namespace to doc (#3009) e9ca7ea47 Suppress a bogus warning 81f1cc74a Improve Unicode handling when writing to an ostream on Windows (#2994) bbcb129e0 Reduce filesize of the tests on MinGW (#2995) 48e0a5922 Implement compile-time checks for dynamic width/precision type bc5c7c50f Fixes IBM XLC behavior with uint128 fallback (#2985) 00adc7120 Add a regression test for here be dragons c48be439f Add additional search paths for doxygen on Windows 371f9c71c Fix even in format_dragon 91abfcd6c Suppress an msvc warning deeab54b4 Remove unused include 688a627d6 Remove unused constants. 9bb1605f1 Remove some branches 8061d9afb Simplify Dragonbox implementation d82e1a108 Make sure the correct fmod overload is called defa04e73 Fix for EDG frontend (Intel, NVHPC compilers) (#2982) 92d36e82c The n specifier for ranges (#2981) 0db43cf7f Pointless comparison warnings (#2971) 05be7a076 Use FMT_USE_FLOAT128 instead of __SIZEOF_FLOAT128__ 2a1b3ac62 Fix large shift in uint128_fallback e1d3d3a32 Exclude recursive ranges from the formatter specialization for ranges (#2974) b761f1279 Improve forward using the idea from Jonathan Müller cc1926942 Inline std::forward d5e9166f5 Fixed typo in changelog example. b31d1a75a Add xchar support for fmt::streamed(). git-subtree-dir: externals/fmt git-subtree-split: f5e54359df4c26b6230fc61d38aa294581393084
This commit is contained in:
parent
a7f9129f18
commit
091ca2aa03
76 changed files with 6841 additions and 4838 deletions
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions" # Necessary to update action hashs
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
# Allow up to 3 opened pull requests for github-actions versions
|
||||||
|
open-pull-requests-limit: 3
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
|
@ -1,6 +1,7 @@
|
||||||
<!--
|
<!--
|
||||||
Please read the contribution guidelines before submitting a pull request:
|
Please read the contribution guidelines before submitting a pull request:
|
||||||
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
|
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
|
||||||
By submitting this pull request, you agree that your contributions are licensed
|
By submitting this pull request, you agree to license your contribution(s)
|
||||||
under the {fmt} license, and agree to future changes to the licensing.
|
under the terms outlined in LICENSE.rst and represent that you have the right
|
||||||
|
to do so.
|
||||||
-->
|
-->
|
||||||
|
|
30
.github/workflows/cifuzz.yml
vendored
Normal file
30
.github/workflows/cifuzz.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
name: CIFuzz
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Fuzzing:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Build Fuzzers
|
||||||
|
id: build
|
||||||
|
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||||
|
with:
|
||||||
|
oss-fuzz-project-name: 'fmt'
|
||||||
|
dry-run: false
|
||||||
|
language: c++
|
||||||
|
- name: Run Fuzzers
|
||||||
|
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||||
|
with:
|
||||||
|
oss-fuzz-project-name: 'fmt'
|
||||||
|
fuzz-seconds: 300
|
||||||
|
dry-run: false
|
||||||
|
language: c++
|
||||||
|
- name: Upload Crash
|
||||||
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
|
if: failure() && steps.build.outcome == 'success'
|
||||||
|
with:
|
||||||
|
name: artifacts
|
||||||
|
path: ./out/artifacts
|
10
.github/workflows/doc.yml
vendored
10
.github/workflows/doc.yml
vendored
|
@ -11,7 +11,15 @@ jobs:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
|
||||||
|
- name: Add ubuntu mirrors
|
||||||
|
run: |
|
||||||
|
# Github Actions caching proxy is at times unreliable
|
||||||
|
# see https://github.com/actions/runner-images/issues/7048
|
||||||
|
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
|
||||||
|
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
|
||||||
|
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: |
|
run: |
|
||||||
|
|
43
.github/workflows/linux.yml
vendored
43
.github/workflows/linux.yml
vendored
|
@ -7,66 +7,83 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
cxx: [g++-4.8, g++-10, clang++-9]
|
cxx: [g++-4.8, g++-10, clang++-9]
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug, Release]
|
||||||
std: [11]
|
std: [11]
|
||||||
os: [ubuntu-18.04]
|
|
||||||
include:
|
include:
|
||||||
- cxx: g++-4.8
|
- cxx: g++-4.8
|
||||||
install: sudo apt install g++-4.8
|
install: sudo apt install g++-4.8
|
||||||
os: ubuntu-18.04
|
|
||||||
- cxx: g++-8
|
- cxx: g++-8
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 14
|
std: 14
|
||||||
install: sudo apt install g++-8
|
install: sudo apt install g++-8
|
||||||
os: ubuntu-18.04
|
|
||||||
- cxx: g++-8
|
- cxx: g++-8
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 17
|
std: 17
|
||||||
install: sudo apt install g++-8
|
install: sudo apt install g++-8
|
||||||
os: ubuntu-18.04
|
- cxx: g++-9
|
||||||
|
build_type: Debug
|
||||||
|
std: 17
|
||||||
- cxx: g++-10
|
- cxx: g++-10
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 17
|
std: 17
|
||||||
os: ubuntu-18.04
|
|
||||||
- cxx: g++-11
|
- cxx: g++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 20
|
std: 20
|
||||||
os: ubuntu-20.04
|
|
||||||
install: sudo apt install g++-11
|
install: sudo apt install g++-11
|
||||||
- cxx: clang++-8
|
- cxx: clang++-8
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 17
|
std: 17
|
||||||
cxxflags: -stdlib=libc++
|
cxxflags: -stdlib=libc++
|
||||||
os: ubuntu-18.04
|
|
||||||
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
|
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
|
||||||
|
- cxx: clang++-9
|
||||||
|
install: sudo apt install clang-9
|
||||||
- cxx: clang++-9
|
- cxx: clang++-9
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||||
std: 17
|
std: 17
|
||||||
os: ubuntu-18.04
|
install: sudo apt install clang-9
|
||||||
- cxx: clang++-11
|
- cxx: clang++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 20
|
std: 20
|
||||||
os: ubuntu-20.04
|
|
||||||
- cxx: clang++-11
|
- cxx: clang++-11
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
std: 20
|
std: 20
|
||||||
cxxflags: -stdlib=libc++
|
cxxflags: -stdlib=libc++
|
||||||
os: ubuntu-20.04
|
|
||||||
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
||||||
- shared: -DBUILD_SHARED_LIBS=ON
|
- shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
|
||||||
|
- name: Set timezone
|
||||||
|
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
|
||||||
|
|
||||||
|
- name: Add repositories for older GCC
|
||||||
|
run: |
|
||||||
|
# Below two repos provide GCC 4.8, 5.5 and 6.4
|
||||||
|
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main'
|
||||||
|
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe'
|
||||||
|
# Below two repos additionally update GCC 6 to 6.5
|
||||||
|
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main'
|
||||||
|
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe'
|
||||||
|
if: ${{ matrix.cxx == 'g++-4.8' }}
|
||||||
|
|
||||||
|
- name: Add ubuntu mirrors
|
||||||
|
run: |
|
||||||
|
# Github Actions caching proxy is at times unreliable
|
||||||
|
# see https://github.com/actions/runner-images/issues/7048
|
||||||
|
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
|
||||||
|
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
|
||||||
|
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: |
|
run: |
|
||||||
${{matrix.install}}
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
|
${{matrix.install}}
|
||||||
sudo apt install locales-all
|
sudo apt install locales-all
|
||||||
cmake -E make_directory ${{runner.workspace}}/build
|
cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
|
19
.github/workflows/macos.yml
vendored
19
.github/workflows/macos.yml
vendored
|
@ -7,15 +7,29 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-10.15
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
os: [macos-11, macos-13]
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug, Release]
|
||||||
|
std: [11, 17, 20]
|
||||||
|
exclude:
|
||||||
|
- { os: macos-11, std: 20 }
|
||||||
|
- { os: macos-13, std: 11 }
|
||||||
|
- { os: macos-13, std: 17 }
|
||||||
include:
|
include:
|
||||||
- shared: -DBUILD_SHARED_LIBS=ON
|
- shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
|
runs-on: '${{ matrix.os }}'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
|
||||||
|
- name: Set timezone
|
||||||
|
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
|
||||||
|
|
||||||
|
- name: Select Xcode 14.3 (macOS 13)
|
||||||
|
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"
|
||||||
|
if: ${{ matrix.os == 'macos-13' }}
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
@ -24,6 +38,7 @@ jobs:
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
run: |
|
run: |
|
||||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||||
|
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||||
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
|
65
.github/workflows/scorecard.yml
vendored
Normal file
65
.github/workflows/scorecard.yml
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||||
|
# by a third-party and are governed by separate terms of service, privacy
|
||||||
|
# policy, and support documentation.
|
||||||
|
|
||||||
|
name: Scorecard supply-chain security
|
||||||
|
on:
|
||||||
|
# For Branch-Protection check. Only the default branch is supported. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||||
|
branch_protection_rule:
|
||||||
|
# To guarantee Maintained check is occasionally updated. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||||
|
schedule:
|
||||||
|
- cron: '26 14 * * 5'
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
# Declare default permissions as read only.
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Scorecard analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
# Needed to upload the results to code-scanning dashboard.
|
||||||
|
security-events: write
|
||||||
|
# Needed to publish results and get a badge (see publish_results below).
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: "Run analysis"
|
||||||
|
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
|
||||||
|
with:
|
||||||
|
results_file: results.sarif
|
||||||
|
results_format: sarif
|
||||||
|
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||||
|
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||||
|
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||||
|
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||||
|
|
||||||
|
# Public repositories:
|
||||||
|
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||||
|
# - Allows the repository to include the Scorecard badge.
|
||||||
|
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||||
|
publish_results: true
|
||||||
|
|
||||||
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
|
# format to the repository Actions tab.
|
||||||
|
- name: "Upload artifact"
|
||||||
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
|
with:
|
||||||
|
name: SARIF file
|
||||||
|
path: results.sarif
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
|
- name: "Upload to code-scanning"
|
||||||
|
uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
67
.github/workflows/windows.yml
vendored
67
.github/workflows/windows.yml
vendored
|
@ -15,27 +15,36 @@ jobs:
|
||||||
# https://github.com/actions/virtual-environments.
|
# https://github.com/actions/virtual-environments.
|
||||||
os: [windows-2019]
|
os: [windows-2019]
|
||||||
platform: [Win32, x64]
|
platform: [Win32, x64]
|
||||||
|
toolset: [v140, v141, v142]
|
||||||
|
standard: [14, 17, 20]
|
||||||
|
shared: ["", -DBUILD_SHARED_LIBS=ON]
|
||||||
build_type: [Debug, Release]
|
build_type: [Debug, Release]
|
||||||
standard: [11, 17, 20]
|
exclude:
|
||||||
|
- { toolset: v140, standard: 17 }
|
||||||
|
- { toolset: v140, standard: 20 }
|
||||||
|
- { toolset: v141, standard: 14 }
|
||||||
|
- { toolset: v141, standard: 20 }
|
||||||
|
- { toolset: v142, standard: 14 }
|
||||||
|
- { platform: Win32, toolset: v140 }
|
||||||
|
- { platform: Win32, toolset: v141 }
|
||||||
|
- { platform: Win32, standard: 14 }
|
||||||
|
- { platform: Win32, standard: 20 }
|
||||||
|
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
include:
|
include:
|
||||||
- os: windows-2019
|
|
||||||
platform: Win32
|
|
||||||
build_type: Debug
|
|
||||||
shared: -DBUILD_SHARED_LIBS=ON
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
platform: x64
|
platform: x64
|
||||||
|
toolset: v143
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
standard: 20
|
standard: 20
|
||||||
exclude:
|
|
||||||
- os: windows-2019
|
|
||||||
standard: 11
|
|
||||||
platform: Win32
|
|
||||||
- os: windows-2019
|
|
||||||
standard: 20
|
|
||||||
platform: Win32
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
|
||||||
|
- name: Set timezone
|
||||||
|
run: tzutil /s "Ekaterinburg Standard Time"
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
@ -45,9 +54,9 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
run: |
|
run: |
|
||||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \
|
||||||
-A ${{matrix.platform}} \
|
|
||||||
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
|
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
|
||||||
|
${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||||
$GITHUB_WORKSPACE
|
$GITHUB_WORKSPACE
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
|
@ -61,3 +70,31 @@ jobs:
|
||||||
run: ctest -C ${{matrix.build_type}} -V
|
run: ctest -C ${{matrix.build_type}} -V
|
||||||
env:
|
env:
|
||||||
CTEST_OUTPUT_ON_FAILURE: True
|
CTEST_OUTPUT_ON_FAILURE: True
|
||||||
|
|
||||||
|
mingw:
|
||||||
|
runs-on: windows-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: msys2 {0}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
sys: [ mingw64, ucrt64 ]
|
||||||
|
steps:
|
||||||
|
- name: Set timezone
|
||||||
|
run: tzutil /s "Ekaterinburg Standard Time"
|
||||||
|
shell: cmd
|
||||||
|
- uses: msys2/setup-msys2@7efe20baefed56359985e327d329042cde2434ff # v2
|
||||||
|
with:
|
||||||
|
release: false
|
||||||
|
msystem: ${{matrix.sys}}
|
||||||
|
pacboy: cc:p cmake:p ninja:p lld:p
|
||||||
|
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
env: { LDFLAGS: -fuse-ld=lld }
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ../build
|
||||||
|
- name: Test
|
||||||
|
run: ctest -j `nproc` --test-dir ../build
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
||||||
|
|
145
CMakeLists.txt
145
CMakeLists.txt
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.1...3.18)
|
cmake_minimum_required(VERSION 3.8...3.26)
|
||||||
|
|
||||||
# Fallback for using newer policies on CMake <3.12.
|
# Fallback for using newer policies on CMake <3.12.
|
||||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
|
@ -24,15 +24,86 @@ function(join result_var)
|
||||||
set(${result_var} "${result}" PARENT_SCOPE)
|
set(${result_var} "${result}" PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# DEPRECATED! Should be merged into add_module_library.
|
||||||
function(enable_module target)
|
function(enable_module target)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||||
target_compile_options(${target}
|
target_compile_options(${target}
|
||||||
PRIVATE /interface /ifcOutput ${BMI}
|
PRIVATE /interface /ifcOutput ${BMI}
|
||||||
INTERFACE /reference fmt=${BMI})
|
INTERFACE /reference fmt=${BMI})
|
||||||
|
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||||
|
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||||
endif ()
|
endif ()
|
||||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
endfunction()
|
||||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
|
||||||
|
# Adds a library compiled with C++20 module support.
|
||||||
|
# `enabled` is a CMake variables that specifies if modules are enabled.
|
||||||
|
# If modules are disabled `add_module_library` falls back to creating a
|
||||||
|
# non-modular library.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled])
|
||||||
|
function(add_module_library name)
|
||||||
|
cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN})
|
||||||
|
set(sources ${AML_UNPARSED_ARGUMENTS})
|
||||||
|
|
||||||
|
add_library(${name})
|
||||||
|
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX)
|
||||||
|
|
||||||
|
if (NOT ${${AML_IF}})
|
||||||
|
# Create a non-modular library.
|
||||||
|
target_sources(${name} PRIVATE ${AML_FALLBACK})
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Modules require C++20.
|
||||||
|
target_compile_features(${name} PUBLIC cxx_std_20)
|
||||||
|
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||||
|
target_compile_options(${name} PUBLIC -fmodules-ts)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# `std` is affected by CMake options and may be higher than C++20.
|
||||||
|
get_target_property(std ${name} CXX_STANDARD)
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
set(pcms)
|
||||||
|
foreach (src ${sources})
|
||||||
|
get_filename_component(pcm ${src} NAME_WE)
|
||||||
|
set(pcm ${pcm}.pcm)
|
||||||
|
|
||||||
|
# Propagate -fmodule-file=*.pcm to targets that link with this library.
|
||||||
|
target_compile_options(
|
||||||
|
${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm})
|
||||||
|
|
||||||
|
# Use an absolute path to prevent target_link_libraries prepending -l
|
||||||
|
# to it.
|
||||||
|
set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm})
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${pcm}
|
||||||
|
COMMAND ${CMAKE_CXX_COMPILER}
|
||||||
|
-std=c++${std} -x c++-module --precompile -c
|
||||||
|
-o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src}
|
||||||
|
"-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>"
|
||||||
|
# Required by the -I generator expression above.
|
||||||
|
COMMAND_EXPAND_LISTS
|
||||||
|
DEPENDS ${src})
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
# Add .pcm files as sources to make sure they are built before the library.
|
||||||
|
set(sources)
|
||||||
|
foreach (pcm ${pcms})
|
||||||
|
get_filename_component(pcm_we ${pcm} NAME_WE)
|
||||||
|
set(obj ${pcm_we}.o)
|
||||||
|
# Use an absolute path to prevent target_link_libraries prepending -l.
|
||||||
|
set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj})
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${obj}
|
||||||
|
COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS>
|
||||||
|
-c -o ${obj} ${pcm}
|
||||||
|
DEPENDS ${pcm})
|
||||||
|
endforeach ()
|
||||||
|
endif ()
|
||||||
|
target_sources(${name} PRIVATE ${sources})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
include(CMakeParseArguments)
|
include(CMakeParseArguments)
|
||||||
|
@ -75,7 +146,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||||
|
|
||||||
# Options that control generation of various targets.
|
# Options that control generation of various targets.
|
||||||
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
option(FMT_INSTALL "Generate the install target." ON)
|
||||||
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||||
|
@ -83,16 +154,6 @@ option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||||
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||||
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
||||||
|
|
||||||
set(FMT_CAN_MODULE OFF)
|
|
||||||
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
|
||||||
# msvc 16.10-pre4
|
|
||||||
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
|
||||||
set(FMT_CAN_MODULE OFF)
|
|
||||||
endif ()
|
|
||||||
if (NOT FMT_CAN_MODULE)
|
|
||||||
set(FMT_MODULE OFF)
|
|
||||||
message(STATUS "Module support is disabled.")
|
|
||||||
endif ()
|
|
||||||
if (FMT_TEST AND FMT_MODULE)
|
if (FMT_TEST AND FMT_MODULE)
|
||||||
# The tests require {fmt} to be compiled as traditional library
|
# The tests require {fmt} to be compiled as traditional library
|
||||||
message(STATUS "Testing is incompatible with build mode 'module'.")
|
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||||
|
@ -101,6 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
|
||||||
if (FMT_SYSTEM_HEADERS)
|
if (FMT_SYSTEM_HEADERS)
|
||||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
||||||
endif ()
|
endif ()
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
|
||||||
|
set(FMT_TEST OFF)
|
||||||
|
message(STATUS "MSDOS is incompatible with gtest")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Get version from core.h
|
# Get version from core.h
|
||||||
file(READ include/fmt/core.h core_h)
|
file(READ include/fmt/core.h core_h)
|
||||||
|
@ -118,23 +183,15 @@ message(STATUS "Version: ${FMT_VERSION}")
|
||||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||||
|
|
||||||
include(cxx14)
|
include(CheckCXXCompilerFlag)
|
||||||
include(JoinPaths)
|
include(JoinPaths)
|
||||||
|
|
||||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
|
||||||
if (${index} GREATER -1)
|
|
||||||
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
|
||||||
# compatibility with older CMake versions.
|
|
||||||
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
|
||||||
endif ()
|
|
||||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
|
||||||
|
|
||||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||||
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||||
"Preset for the export of private symbols")
|
"Preset for the export of private symbols")
|
||||||
|
@ -220,16 +277,18 @@ endfunction()
|
||||||
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||||
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
||||||
xchar.h)
|
xchar.h)
|
||||||
if (FMT_MODULE)
|
set(FMT_SOURCES src/format.cc)
|
||||||
set(FMT_SOURCES src/fmt.cc)
|
if (FMT_OS)
|
||||||
elseif (FMT_OS)
|
set(FMT_SOURCES ${FMT_SOURCES} src/os.cc)
|
||||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
|
||||||
else()
|
|
||||||
set(FMT_SOURCES src/format.cc)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
add_module_library(fmt src/fmt.cc FALLBACK
|
||||||
|
${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst
|
||||||
|
IF FMT_MODULE)
|
||||||
add_library(fmt::fmt ALIAS fmt)
|
add_library(fmt::fmt ALIAS fmt)
|
||||||
|
if (FMT_MODULE)
|
||||||
|
enable_module(fmt)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (FMT_WERROR)
|
if (FMT_WERROR)
|
||||||
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
||||||
|
@ -237,11 +296,12 @@ endif ()
|
||||||
if (FMT_PEDANTIC)
|
if (FMT_PEDANTIC)
|
||||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||||
endif ()
|
endif ()
|
||||||
if (FMT_MODULE)
|
|
||||||
enable_module(fmt)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||||
|
target_compile_features(fmt PUBLIC cxx_std_11)
|
||||||
|
else ()
|
||||||
|
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
|
||||||
|
endif ()
|
||||||
|
|
||||||
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
|
@ -262,13 +322,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (BUILD_SHARED_LIBS)
|
if (BUILD_SHARED_LIBS)
|
||||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
|
target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
|
||||||
NOT EMSCRIPTEN)
|
|
||||||
# Fix rpmlint warning:
|
|
||||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
|
||||||
target_link_libraries(fmt -Wl,--as-needed)
|
|
||||||
endif ()
|
|
||||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
|
||||||
endif ()
|
endif ()
|
||||||
if (FMT_SAFE_DURATION_CAST)
|
if (FMT_SAFE_DURATION_CAST)
|
||||||
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
||||||
|
@ -278,7 +332,7 @@ add_library(fmt-header-only INTERFACE)
|
||||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||||
|
|
||||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
|
||||||
|
|
||||||
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
|
@ -300,7 +354,7 @@ if (FMT_INSTALL)
|
||||||
"Installation directory for libraries, a relative path that "
|
"Installation directory for libraries, a relative path that "
|
||||||
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||||
|
|
||||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING
|
||||||
"Installation directory for pkgconfig (.pc) files, a relative "
|
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||||
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||||
"absolute path.")
|
"absolute path.")
|
||||||
|
@ -330,7 +384,6 @@ if (FMT_INSTALL)
|
||||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||||
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
||||||
FRAMEWORK DESTINATION "."
|
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
# Use a namespace because CMake provides better diagnostics for namespaced
|
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||||
|
@ -345,8 +398,6 @@ if (FMT_INSTALL)
|
||||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||||
NAMESPACE fmt::)
|
NAMESPACE fmt::)
|
||||||
|
|
||||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
|
||||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
|
||||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|
797
ChangeLog.rst
797
ChangeLog.rst
|
@ -1,3 +1,796 @@
|
||||||
|
10.1.1 - 2023-08-26
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
* Added formatters for ``std::atomic`` and ``atomic_flag``
|
||||||
|
(`#3574 <https://github.com/fmtlib/fmt/pull/3574>`_,
|
||||||
|
`#3594 <https://github.com/fmtlib/fmt/pull/3594>`_).
|
||||||
|
Thanks `@wangzw (Zhanwei Wang) <https://github.com/wangzw>`_ and
|
||||||
|
`@AlexGuteniev (Alex Guteniev) <https://github.com/AlexGuteniev>`_.
|
||||||
|
|
||||||
|
* Fixed an error about partial specialization of ``formatter<string>``
|
||||||
|
after instantiation when compiled with gcc and C++20
|
||||||
|
(`#3584 <https://github.com/fmtlib/fmt/issues/3584>`_).
|
||||||
|
|
||||||
|
* Fixed compilation as a C++20 module with gcc and clang
|
||||||
|
(`#3587 <https://github.com/fmtlib/fmt/issues/3587>`_,
|
||||||
|
`#3597 <https://github.com/fmtlib/fmt/pull/3597>`_,
|
||||||
|
`#3605 <https://github.com/fmtlib/fmt/pull/3605>`_). Thanks
|
||||||
|
`@MathewBensonCode (Mathew Benson) <https://github.com/MathewBensonCode>`_.
|
||||||
|
|
||||||
|
* Made ``fmt::to_string`` work with types that have ``format_as`` overloads
|
||||||
|
(`#3575 <https://github.com/fmtlib/fmt/pull/3575>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Made ``formatted_size`` work with integral format specifiers at compile time
|
||||||
|
(`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_).
|
||||||
|
Thanks `@elbeno (Ben Deane) <https://github.com/elbeno>`_.
|
||||||
|
|
||||||
|
* Fixed a warning about the ``no_unique_address`` attribute on clang-cl
|
||||||
|
(`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_).
|
||||||
|
Thanks `@lukester1975 <https://github.com/lukester1975>`_.
|
||||||
|
|
||||||
|
* Improved compatibility with the legacy GBK encoding
|
||||||
|
(`#3598 <https://github.com/fmtlib/fmt/issues/3598>`_,
|
||||||
|
`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_).
|
||||||
|
Thanks `@YuHuanTin <https://github.com/YuHuanTin>`_.
|
||||||
|
|
||||||
|
* Added OpenSSF Scorecard analysis
|
||||||
|
(`#3530 <https://github.com/fmtlib/fmt/issues/3530>`_,
|
||||||
|
`#3571 <https://github.com/fmtlib/fmt/pull/3571>`_).
|
||||||
|
Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_.
|
||||||
|
|
||||||
|
* Updated CI dependencies
|
||||||
|
(`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_,
|
||||||
|
`#3592 <https://github.com/fmtlib/fmt/pull/3592>`_,
|
||||||
|
`#3593 <https://github.com/fmtlib/fmt/pull/3593>`_,
|
||||||
|
`#3602 <https://github.com/fmtlib/fmt/pull/3602>`_).
|
||||||
|
|
||||||
|
10.1.0 - 2023-08-12
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
* Optimized format string compilation resulting in up to 40% speed up in
|
||||||
|
compiled ``format_to`` and ~4x speed up in compiled ``format_to_n`` on a
|
||||||
|
concatenation benchmark (`#3133 <https://github.com/fmtlib/fmt/issues/3133>`_,
|
||||||
|
`#3484 <https://github.com/fmtlib/fmt/issues/3484>`_).
|
||||||
|
|
||||||
|
{fmt} 10.0::
|
||||||
|
|
||||||
|
---------------------------------------------------------
|
||||||
|
Benchmark Time CPU Iterations
|
||||||
|
---------------------------------------------------------
|
||||||
|
BM_format_to 78.9 ns 78.9 ns 8881746
|
||||||
|
BM_format_to_n 568 ns 568 ns 1232089
|
||||||
|
|
||||||
|
{fmt} 10.1::
|
||||||
|
|
||||||
|
---------------------------------------------------------
|
||||||
|
Benchmark Time CPU Iterations
|
||||||
|
---------------------------------------------------------
|
||||||
|
BM_format_to 54.9 ns 54.9 ns 12727944
|
||||||
|
BM_format_to_n 133 ns 133 ns 5257795
|
||||||
|
|
||||||
|
* Optimized storage of an empty allocator in ``basic_memory_buffer``
|
||||||
|
(`#3485 <https://github.com/fmtlib/fmt/pull/3485>`_).
|
||||||
|
Thanks `@Minty-Meeo <https://github.com/Minty-Meeo>`_.
|
||||||
|
|
||||||
|
* Added formatters for proxy references to elements of ``std::vector<bool>`` and
|
||||||
|
``std::bitset<N>`` (`#3567 <https://github.com/fmtlib/fmt/issues/3567>`_,
|
||||||
|
`#3570 <https://github.com/fmtlib/fmt/pull/3570>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/zYb79Pvn8>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <fmt/std.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto v = std::vector<bool>{true};
|
||||||
|
fmt::print("{}", v[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
|
||||||
|
`@felix642 (Félix-Antoine Constantin) <https://github.com/felix642>`_.
|
||||||
|
|
||||||
|
* Fixed an ambiguous formatter specialization for containers that look like
|
||||||
|
container adaptors such as ``boost::flat_set``
|
||||||
|
(`#3556 <https://github.com/fmtlib/fmt/issues/3556>`_,
|
||||||
|
`#3561 <https://github.com/fmtlib/fmt/pull/3561>`_).
|
||||||
|
Thanks `@5chmidti <https://github.com/5chmidti>`_.
|
||||||
|
|
||||||
|
* Fixed compilation when formatting durations not convertible from
|
||||||
|
``std::chrono::seconds`` (`#3430 <https://github.com/fmtlib/fmt/pull/3430>`_).
|
||||||
|
Thanks `@patlkli (Patrick Geltinger) <https://github.com/patlkli>`_.
|
||||||
|
|
||||||
|
* Made the ``formatter`` specialization for ``char*`` const-correct
|
||||||
|
(`#3432 <https://github.com/fmtlib/fmt/pull/3432>`_).
|
||||||
|
Thanks `@timsong-cpp <https://github.com/timsong-cpp>`_.
|
||||||
|
|
||||||
|
* Made ``{}`` and ``{:}`` handled consistently during compile-time checks
|
||||||
|
(`#3526 <https://github.com/fmtlib/fmt/issues/3526>`_).
|
||||||
|
|
||||||
|
* Disallowed passing temporaries to ``make_format_args`` to improve API safety
|
||||||
|
by preventing dangling references.
|
||||||
|
|
||||||
|
* Improved the compile-time error for unformattable types
|
||||||
|
(`#3478 <https://github.com/fmtlib/fmt/pull/3478>`_).
|
||||||
|
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||||
|
|
||||||
|
* Improved the floating-point formatter
|
||||||
|
(`#3448 <https://github.com/fmtlib/fmt/pull/3448>`_,
|
||||||
|
`#3450 <https://github.com/fmtlib/fmt/pull/3450>`_).
|
||||||
|
Thanks `@florimond-collette (Florimond Collette)
|
||||||
|
<https://github.com/florimond-collette>`_.
|
||||||
|
|
||||||
|
* Fixed handling of precision for ``long double`` larger than 64 bits.
|
||||||
|
(`#3539 <https://github.com/fmtlib/fmt/issues/3539>`_,
|
||||||
|
`#3564 <https://github.com/fmtlib/fmt/issues/3564>`_).
|
||||||
|
|
||||||
|
* Made floating-point and chrono tests less platform-dependent
|
||||||
|
(`#3337 <https://github.com/fmtlib/fmt/issues/3337>`_,
|
||||||
|
`#3433 <https://github.com/fmtlib/fmt/issues/3433>`_,
|
||||||
|
`#3434 <https://github.com/fmtlib/fmt/pull/3434>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Removed the remnants of the Grisu floating-point formatter that has been
|
||||||
|
replaced by Dragonbox in earlier versions.
|
||||||
|
|
||||||
|
* Added ``throw_format_error`` to the public API
|
||||||
|
(`#3551 <https://github.com/fmtlib/fmt/pull/3551>`_).
|
||||||
|
Thanks `@mjerabek (Martin Jeřábek) <https://github.com/mjerabek>`_.
|
||||||
|
|
||||||
|
* Made ``FMT_THROW`` assert even if assertions are disabled when compiling with
|
||||||
|
exceptions disabled (`#3418 <https://github.com/fmtlib/fmt/issues/3418>`_,
|
||||||
|
`#3439 <https://github.com/fmtlib/fmt/pull/3439>`_).
|
||||||
|
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||||
|
|
||||||
|
* Made ``format_as`` and ``std::filesystem::path`` formatter work with exotic
|
||||||
|
code unit types.
|
||||||
|
(`#3457 <https://github.com/fmtlib/fmt/pull/3457>`_,
|
||||||
|
`#3476 <https://github.com/fmtlib/fmt/pull/3476>`_).
|
||||||
|
Thanks `@gix (Nico Rieck) <https://github.com/gix>`_,
|
||||||
|
`@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
|
||||||
|
|
||||||
|
* Added support for the ``?`` format specifier to ``std::filesystem::path`` and
|
||||||
|
made the default unescaped for consistency with strings.
|
||||||
|
|
||||||
|
* Deprecated the wide stream overload of ``printf``.
|
||||||
|
|
||||||
|
* Removed unused ``basic_printf_parse_context``.
|
||||||
|
|
||||||
|
* Improved RTTI detection used when formatting exceptions
|
||||||
|
(`#3468 <https://github.com/fmtlib/fmt/pull/3468>`_).
|
||||||
|
Thanks `@danakj (Dana Jansens) <https://github.com/danakj>`_.
|
||||||
|
|
||||||
|
* Improved compatibility with VxWorks7
|
||||||
|
(`#3467 <https://github.com/fmtlib/fmt/pull/3467>`_).
|
||||||
|
Thanks `@wenshan1 (Bin Lan) <https://github.com/wenshan1>`_.
|
||||||
|
|
||||||
|
* Improved documentation
|
||||||
|
(`#3174 <https://github.com/fmtlib/fmt/issues/3174>`_,
|
||||||
|
`#3423 <https://github.com/fmtlib/fmt/issues/3423>`_,
|
||||||
|
`#3454 <https://github.com/fmtlib/fmt/pull/3454>`_,
|
||||||
|
`#3458 <https://github.com/fmtlib/fmt/issues/3458>`_,
|
||||||
|
`#3461 <https://github.com/fmtlib/fmt/pull/3461>`_,
|
||||||
|
`#3487 <https://github.com/fmtlib/fmt/issues/3487>`_,
|
||||||
|
`#3515 <https://github.com/fmtlib/fmt/pull/3515>`_).
|
||||||
|
Thanks `@zencatalyst (Kasra Hashemi) <https://github.com/zencatalyst>`_,
|
||||||
|
`@rlalik <https://github.com/rlalik>`_,
|
||||||
|
`@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_.
|
||||||
|
|
||||||
|
* Improved build and CI configurations
|
||||||
|
(`#3449 <https://github.com/fmtlib/fmt/issues/3449>`_,
|
||||||
|
`#3451 <https://github.com/fmtlib/fmt/pull/3451>`_,
|
||||||
|
`#3452 <https://github.com/fmtlib/fmt/pull/3452>`_,
|
||||||
|
`#3453 <https://github.com/fmtlib/fmt/pull/3453>`_,
|
||||||
|
`#3459 <https://github.com/fmtlib/fmt/pull/3459>`_,
|
||||||
|
`#3481 <https://github.com/fmtlib/fmt/issues/3481>`_,
|
||||||
|
`#3486 <https://github.com/fmtlib/fmt/pull/3486>`_,
|
||||||
|
`#3489 <https://github.com/fmtlib/fmt/issues/3489>`_,
|
||||||
|
`#3496 <https://github.com/fmtlib/fmt/pull/3496>`_,
|
||||||
|
`#3517 <https://github.com/fmtlib/fmt/issues/3517>`_,
|
||||||
|
`#3523 <https://github.com/fmtlib/fmt/pull/3523>`_,
|
||||||
|
`#3563 <https://github.com/fmtlib/fmt/pull/3563>`_).
|
||||||
|
Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_,
|
||||||
|
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
|
||||||
|
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||||
|
`@petrmanek (Petr Mánek) <https://github.com/petrmanek>`_,
|
||||||
|
`@setoye (Alta) <https://github.com/setoye>`_,
|
||||||
|
`@abouvier (Alexandre Bouvier) <https://github.com/abouvier>`_.
|
||||||
|
|
||||||
|
* Fixed various warnings and compilation issues
|
||||||
|
(`#3408 <https://github.com/fmtlib/fmt/issues/3408>`_,
|
||||||
|
`#3424 <https://github.com/fmtlib/fmt/issues/3424>`_,
|
||||||
|
`#3444 <https://github.com/fmtlib/fmt/issues/3444>`_,
|
||||||
|
`#3446 <https://github.com/fmtlib/fmt/pull/3446>`_,
|
||||||
|
`#3475 <https://github.com/fmtlib/fmt/pull/3475>`_,
|
||||||
|
`#3482 <https://github.com/fmtlib/fmt/pull/3482>`_,
|
||||||
|
`#3492 <https://github.com/fmtlib/fmt/issues/3492>`_,
|
||||||
|
`#3493 <https://github.com/fmtlib/fmt/pull/3493>`_,
|
||||||
|
`#3508 <https://github.com/fmtlib/fmt/pull/3508>`_,
|
||||||
|
`#3509 <https://github.com/fmtlib/fmt/issues/3509>`_,
|
||||||
|
`#3533 <https://github.com/fmtlib/fmt/issues/3533>`_,
|
||||||
|
`#3542 <https://github.com/fmtlib/fmt/pull/3542>`_,
|
||||||
|
`#3543 <https://github.com/fmtlib/fmt/issues/3543>`_,
|
||||||
|
`#3540 <https://github.com/fmtlib/fmt/issues/3540>`_,
|
||||||
|
`#3544 <https://github.com/fmtlib/fmt/pull/3544>`_,
|
||||||
|
`#3548 <https://github.com/fmtlib/fmt/issues/3548>`_,
|
||||||
|
`#3549 <https://github.com/fmtlib/fmt/pull/3549>`_,
|
||||||
|
`#3550 <https://github.com/fmtlib/fmt/pull/3550>`_,
|
||||||
|
`#3552 <https://github.com/fmtlib/fmt/pull/3552>`_).
|
||||||
|
Thanks `@adesitter (Arnaud Desitter) <https://github.com/adesitter>`_,
|
||||||
|
`@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_,
|
||||||
|
`@Minty-Meeo <https://github.com/Minty-Meeo>`_,
|
||||||
|
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||||
|
`@TobiSchluter (Tobias Schlüter) <https://github.com/TobiSchluter>`_,
|
||||||
|
`@kieranclancy (Kieran Clancy) <https://github.com/kieranclancy>`_,
|
||||||
|
`@alexeedm (Dmitry Alexeev) <https://github.com/alexeedm>`_,
|
||||||
|
`@jurihock (Jürgen Hock) <https://github.com/jurihock>`_,
|
||||||
|
`@Ozomahtli <https://github.com/Ozomahtli>`_,
|
||||||
|
`@razaqq <https://github.com/razaqq>`_.
|
||||||
|
|
||||||
|
10.0.0 - 2023-05-09
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
* Replaced Grisu with a new floating-point formatting algorithm for given
|
||||||
|
precision (`#3262 <https://github.com/fmtlib/fmt/issues/3262>`_,
|
||||||
|
`#2750 <https://github.com/fmtlib/fmt/issues/2750>`_,
|
||||||
|
`#3269 <https://github.com/fmtlib/fmt/pull/3269>`_,
|
||||||
|
`#3276 <https://github.com/fmtlib/fmt/pull/3276>`_).
|
||||||
|
The new algorithm is based on Dragonbox already used for the
|
||||||
|
shortest representation and gives substantial performance improvement:
|
||||||
|
|
||||||
|
.. image:: https://user-images.githubusercontent.com/33922675/
|
||||||
|
211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png
|
||||||
|
|
||||||
|
* Red: new algorithm
|
||||||
|
* Green: new algorithm with ``FMT_USE_FULL_CACHE_DRAGONBOX`` defined to 1
|
||||||
|
* Blue: old algorithm
|
||||||
|
|
||||||
|
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
|
||||||
|
|
||||||
|
* Replaced ``snprintf``-based hex float formatter with an internal
|
||||||
|
implementation (`#3179 <https://github.com/fmtlib/fmt/pull/3179>`_,
|
||||||
|
`#3203 <https://github.com/fmtlib/fmt/pull/3203>`_).
|
||||||
|
This removes the last usage of ``s(n)printf`` in {fmt}.
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Fixed alignment of floating-point numbers with localization
|
||||||
|
(`#3263 <https://github.com/fmtlib/fmt/issues/3263>`_,
|
||||||
|
`#3272 <https://github.com/fmtlib/fmt/pull/3272>`_).
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Made handling of ``#`` consistent with ``std::format``.
|
||||||
|
|
||||||
|
* Improved C++20 module support
|
||||||
|
(`#3134 <https://github.com/fmtlib/fmt/pull/3134>`_,
|
||||||
|
`#3254 <https://github.com/fmtlib/fmt/pull/3254>`_,
|
||||||
|
`#3386 <https://github.com/fmtlib/fmt/pull/3386>`_,
|
||||||
|
`#3387 <https://github.com/fmtlib/fmt/pull/3387>`_,
|
||||||
|
`#3388 <https://github.com/fmtlib/fmt/pull/3388>`_,
|
||||||
|
`#3392 <https://github.com/fmtlib/fmt/pull/3392>`_,
|
||||||
|
`#3397 <https://github.com/fmtlib/fmt/pull/3397>`_,
|
||||||
|
`#3399 <https://github.com/fmtlib/fmt/pull/3399>`_,
|
||||||
|
`#3400 <https://github.com/fmtlib/fmt/pull/3400>`_).
|
||||||
|
Thanks `@laitingsheng (Tinson Lai) <https://github.com/laitingsheng>`_,
|
||||||
|
`@Orvid (Orvid King) <https://github.com/Orvid>`_,
|
||||||
|
`@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
|
||||||
|
Switched to the `modules CMake library <https://github.com/vitaut/modules>`_
|
||||||
|
which allows building {fmt} as a C++20 module with clang::
|
||||||
|
|
||||||
|
CXX=clang++ cmake -DFMT_MODULE=ON .
|
||||||
|
make
|
||||||
|
|
||||||
|
* Made ``format_as`` work with any user-defined type and not just enums.
|
||||||
|
For example (`godbolt <https://godbolt.org/z/b7rqhq5Kh>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
struct floaty_mc_floatface {
|
||||||
|
double value;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto format_as(floaty_mc_floatface f) { return f.value; }
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42"
|
||||||
|
}
|
||||||
|
|
||||||
|
* Removed deprecated implicit conversions for enums and conversions to primitive
|
||||||
|
types for compatibility with ``std::format`` and to prevent potential ODR
|
||||||
|
violations. Use ``format_as`` instead.
|
||||||
|
|
||||||
|
* Added support for fill, align and width to the time point formatter
|
||||||
|
(`#3237 <https://github.com/fmtlib/fmt/issues/3237>`_,
|
||||||
|
`#3260 <https://github.com/fmtlib/fmt/pull/3260>`_,
|
||||||
|
`#3275 <https://github.com/fmtlib/fmt/pull/3275>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/rKP6MGz6c>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// prints " 2023"
|
||||||
|
fmt::print("{:>8%Y}\n", std::chrono::system_clock::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Implemented formatting of subseconds
|
||||||
|
(`#2207 <https://github.com/fmtlib/fmt/issues/2207>`_,
|
||||||
|
`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
|
||||||
|
`#3115 <https://github.com/fmtlib/fmt/pull/3115>`_,
|
||||||
|
`#3143 <https://github.com/fmtlib/fmt/pull/3143>`_,
|
||||||
|
`#3144 <https://github.com/fmtlib/fmt/pull/3144>`_,
|
||||||
|
`#3349 <https://github.com/fmtlib/fmt/pull/3349>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/45738oGEo>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// prints 01.234567
|
||||||
|
fmt::print("{:%S}\n", std::chrono::microseconds(1234567));
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_
|
||||||
|
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||||
|
`@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||||
|
|
||||||
|
* Added precision support to ``%S``
|
||||||
|
(`#3148 <https://github.com/fmtlib/fmt/pull/3148>`_).
|
||||||
|
Thanks `@SappyJoy (Stepan Ponomaryov) <https://github.com/SappyJoy>`_
|
||||||
|
|
||||||
|
* Added support for ``std::utc_time``
|
||||||
|
(`#3098 <https://github.com/fmtlib/fmt/issues/3098>`_,
|
||||||
|
`#3110 <https://github.com/fmtlib/fmt/pull/3110>`_).
|
||||||
|
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_.
|
||||||
|
|
||||||
|
* Switched formatting of ``std::chrono::system_clock`` from local time to UTC
|
||||||
|
for compatibility with the standard
|
||||||
|
(`#3199 <https://github.com/fmtlib/fmt/issues/3199>`_,
|
||||||
|
`#3230 <https://github.com/fmtlib/fmt/pull/3230>`_).
|
||||||
|
Thanks `@ned14 (Niall Douglas) <https://github.com/ned14>`_.
|
||||||
|
|
||||||
|
* Added support for ``%Ez`` and ``%Oz`` to chrono formatters.
|
||||||
|
(`#3220 <https://github.com/fmtlib/fmt/issues/3220>`_,
|
||||||
|
`#3222 <https://github.com/fmtlib/fmt/pull/3222>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Improved validation of format specifiers for ``std::chrono::duration``
|
||||||
|
(`#3219 <https://github.com/fmtlib/fmt/issues/3219>`_,
|
||||||
|
`#3232 <https://github.com/fmtlib/fmt/pull/3232>`_).
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Fixed formatting of time points before the epoch
|
||||||
|
(`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
|
||||||
|
`#3261 <https://github.com/fmtlib/fmt/pull/3261>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto t = std::chrono::system_clock::from_time_t(0) -
|
||||||
|
std::chrono::milliseconds(250);
|
||||||
|
fmt::print("{:%S}\n", t); // prints 59.750000000
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Experimental: implemented glibc extension for padding seconds, minutes and
|
||||||
|
hours (`#2959 <https://github.com/fmtlib/fmt/issues/2959>`_,
|
||||||
|
`#3271 <https://github.com/fmtlib/fmt/pull/3271>`_).
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Added a formatter for ``std::exception``
|
||||||
|
(`#2977 <https://github.com/fmtlib/fmt/issues/2977>`_,
|
||||||
|
`#3012 <https://github.com/fmtlib/fmt/issues/3012>`_,
|
||||||
|
`#3062 <https://github.com/fmtlib/fmt/pull/3062>`_,
|
||||||
|
`#3076 <https://github.com/fmtlib/fmt/pull/3076>`_,
|
||||||
|
`#3119 <https://github.com/fmtlib/fmt/pull/3119>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/8xoWGs9e4>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/std.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
std::vector<bool>().at(0);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
fmt::print("{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prints::
|
||||||
|
|
||||||
|
vector<bool>::_M_range_check: __n (which is 0) >= this->size() (which is 0)
|
||||||
|
|
||||||
|
on libstdc++.
|
||||||
|
Thanks `@zach2good (Zach Toogood) <https://github.com/zach2good>`_ and
|
||||||
|
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Moved ``std::error_code`` formatter from ``fmt/os.h`` to ``fmt/std.h``.
|
||||||
|
(`#3125 <https://github.com/fmtlib/fmt/pull/3125>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Added formatters for standard container adapters: ``std::priority_queue``,
|
||||||
|
``std::queue`` and ``std::stack``
|
||||||
|
(`#3215 <https://github.com/fmtlib/fmt/issues/3215>`_,
|
||||||
|
`#3279 <https://github.com/fmtlib/fmt/pull/3279>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/74h1xY9qK>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/ranges.h>
|
||||||
|
#include <stack>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto s = std::stack<bool, std::vector<bool>>();
|
||||||
|
for (auto b: {true, false, true}) s.push(b);
|
||||||
|
fmt::print("{}\n", s); // prints [true, false, true]
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Added a formatter for ``std::optional`` to ``fmt/std.h``.
|
||||||
|
Thanks `@tom-huntington <https://github.com/tom-huntington>`_.
|
||||||
|
|
||||||
|
* Fixed formatting of valueless by exception variants
|
||||||
|
(`#3347 <https://github.com/fmtlib/fmt/pull/3347>`_).
|
||||||
|
Thanks `@TheOmegaCarrot <https://github.com/TheOmegaCarrot>`_.
|
||||||
|
|
||||||
|
* Made ``fmt::ptr`` accept ``unique_ptr`` with a custom deleter
|
||||||
|
(`#3177 <https://github.com/fmtlib/fmt/pull/3177>`_).
|
||||||
|
Thanks `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
|
||||||
|
|
||||||
|
* Fixed formatting of noncopyable ranges and nested ranges of chars
|
||||||
|
(`#3158 <https://github.com/fmtlib/fmt/pull/3158>`_
|
||||||
|
`#3286 <https://github.com/fmtlib/fmt/issues/3286>`_,
|
||||||
|
`#3290 <https://github.com/fmtlib/fmt/pull/3290>`_).
|
||||||
|
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||||
|
|
||||||
|
* Fixed issues with formatting of paths and ranges of paths
|
||||||
|
(`#3319 <https://github.com/fmtlib/fmt/issues/3319>`_,
|
||||||
|
`#3321 <https://github.com/fmtlib/fmt/pull/3321>`_
|
||||||
|
`#3322 <https://github.com/fmtlib/fmt/issues/3322>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Improved handling of invalid Unicode in paths.
|
||||||
|
|
||||||
|
* Enabled compile-time checks on Apple clang 14 and later
|
||||||
|
(`#3331 <https://github.com/fmtlib/fmt/pull/3331>`_).
|
||||||
|
Thanks `@cloyce (Cloyce D. Spradling) <https://github.com/cloyce>`_.
|
||||||
|
|
||||||
|
* Improved compile-time checks of named arguments
|
||||||
|
(`#3105 <https://github.com/fmtlib/fmt/issues/3105>`_,
|
||||||
|
`#3214 <https://github.com/fmtlib/fmt/pull/3214>`_).
|
||||||
|
Thanks `@rbrich (Radek Brich) <https://github.com/rbrich>`_.
|
||||||
|
|
||||||
|
* Fixed formatting when both alignment and ``0`` are given
|
||||||
|
(`#3236 <https://github.com/fmtlib/fmt/issues/3236>`_,
|
||||||
|
`#3248 <https://github.com/fmtlib/fmt/pull/3248>`_).
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Improved Unicode support in the experimental file API on Windows
|
||||||
|
(`#3234 <https://github.com/fmtlib/fmt/issues/3234>`_,
|
||||||
|
`#3293 <https://github.com/fmtlib/fmt/pull/3293>`_).
|
||||||
|
Thanks `@Fros1er (Froster) <https://github.com/Fros1er>`_.
|
||||||
|
|
||||||
|
* Unified UTF transcoding
|
||||||
|
(`#3416 <https://github.com/fmtlib/fmt/pull/3416>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Added support for UTF-8 digit separators via an experimental locale facet
|
||||||
|
(`#1861 <https://github.com/fmtlib/fmt/issues/1861>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
auto loc = std::locale(
|
||||||
|
std::locale(), new fmt::format_facet<std::locale>("’"));
|
||||||
|
auto s = fmt::format(loc, "{:L}", 1000);
|
||||||
|
|
||||||
|
where ``’`` is U+2019 used as a digit separator in the de_CH locale.
|
||||||
|
|
||||||
|
* Added an overload of ``formatted_size`` that takes a locale
|
||||||
|
(`#3084 <https://github.com/fmtlib/fmt/issues/3084>`_,
|
||||||
|
`#3087 <https://github.com/fmtlib/fmt/pull/3087>`_).
|
||||||
|
Thanks `@gerboengels <https://github.com/gerboengels>`_.
|
||||||
|
|
||||||
|
* Removed the deprecated ``FMT_DEPRECATED_OSTREAM``.
|
||||||
|
|
||||||
|
* Fixed a UB when using a null ``std::string_view`` with ``fmt::to_string``
|
||||||
|
or format string compilation
|
||||||
|
(`#3241 <https://github.com/fmtlib/fmt/issues/3241>`_,
|
||||||
|
`#3244 <https://github.com/fmtlib/fmt/pull/3244>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Added ``starts_with`` to the fallback ``string_view`` implementation
|
||||||
|
(`#3080 <https://github.com/fmtlib/fmt/pull/3080>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Added ``fmt::basic_format_string::get()`` for compatibility with
|
||||||
|
``basic_format_string`` (`#3111 <https://github.com/fmtlib/fmt/pull/3111>`_).
|
||||||
|
Thanks `@huangqinjin <https://github.com/huangqinjin>`_.
|
||||||
|
|
||||||
|
* Added ``println`` for compatibility with C++23
|
||||||
|
(`#3267 <https://github.com/fmtlib/fmt/pull/3267>`_).
|
||||||
|
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
|
||||||
|
|
||||||
|
* Renamed the ``FMT_EXPORT`` macro for shared library usage to
|
||||||
|
``FMT_LIB_EXPORT``.
|
||||||
|
|
||||||
|
* Improved documentation
|
||||||
|
(`#3108 <https://github.com/fmtlib/fmt/issues/3108>`_,
|
||||||
|
`#3169 <https://github.com/fmtlib/fmt/issues/3169>`_,
|
||||||
|
`#3243 <https://github.com/fmtlib/fmt/pull/3243>`_).
|
||||||
|
`#3404 <https://github.com/fmtlib/fmt/pull/3404>`_).
|
||||||
|
Thanks `@Cleroth <https://github.com/Cleroth>`_ and
|
||||||
|
`@Vertexwahn <https://github.com/Vertexwahn>`_.
|
||||||
|
|
||||||
|
* Improved build configuration and tests
|
||||||
|
(`#3118 <https://github.com/fmtlib/fmt/pull/3118>`_,
|
||||||
|
`#3120 <https://github.com/fmtlib/fmt/pull/3120>`_,
|
||||||
|
`#3188 <https://github.com/fmtlib/fmt/pull/3188>`_,
|
||||||
|
`#3189 <https://github.com/fmtlib/fmt/issues/3189>`_,
|
||||||
|
`#3198 <https://github.com/fmtlib/fmt/pull/3198>`_,
|
||||||
|
`#3205 <https://github.com/fmtlib/fmt/pull/3205>`_,
|
||||||
|
`#3207 <https://github.com/fmtlib/fmt/pull/3207>`_,
|
||||||
|
`#3210 <https://github.com/fmtlib/fmt/pull/3210>`_,
|
||||||
|
`#3240 <https://github.com/fmtlib/fmt/pull/3240>`_,
|
||||||
|
`#3256 <https://github.com/fmtlib/fmt/pull/3256>`_,
|
||||||
|
`#3264 <https://github.com/fmtlib/fmt/pull/3264>`_,
|
||||||
|
`#3299 <https://github.com/fmtlib/fmt/issues/3299>`_,
|
||||||
|
`#3302 <https://github.com/fmtlib/fmt/pull/3302>`_,
|
||||||
|
`#3312 <https://github.com/fmtlib/fmt/pull/3312>`_,
|
||||||
|
`#3317 <https://github.com/fmtlib/fmt/issues/3317>`_,
|
||||||
|
`#3328 <https://github.com/fmtlib/fmt/pull/3328>`_,
|
||||||
|
`#3333 <https://github.com/fmtlib/fmt/pull/3333>`_,
|
||||||
|
`#3369 <https://github.com/fmtlib/fmt/pull/3369>`_,
|
||||||
|
`#3373 <https://github.com/fmtlib/fmt/issues/3373>`_,
|
||||||
|
`#3395 <https://github.com/fmtlib/fmt/pull/3395>`_,
|
||||||
|
`#3406 <https://github.com/fmtlib/fmt/pull/3406>`_,
|
||||||
|
`#3411 <https://github.com/fmtlib/fmt/pull/3411>`_).
|
||||||
|
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
|
||||||
|
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||||
|
`@DavidKorczynski <https://github.com/DavidKorczynski>`_,
|
||||||
|
`@ChrisThrasher (Chris Thrasher) <https://github.com/ChrisThrasher>`_,
|
||||||
|
`@FrancoisCarouge (François Carouge) <https://github.com/FrancoisCarouge>`_,
|
||||||
|
`@kennyweiss (Kenny Weiss) <https://github.com/kennyweiss>`_,
|
||||||
|
`@luzpaz <https://github.com/luzpaz>`_,
|
||||||
|
`@codeinred (Alecto Irene Perez) <https://github.com/codeinred>`_,
|
||||||
|
`@Mixaill (Mikhail Paulyshka) <https://github.com/Mixaill>`_,
|
||||||
|
`@joycebrum (Joyce) <https://github.com/joycebrum>`_,
|
||||||
|
`@kevinhwang (Kevin Hwang) <https://github.com/kevinhwang>`_,
|
||||||
|
`@Vertexwahn <https://github.com/Vertexwahn>`_.
|
||||||
|
|
||||||
|
* Fixed a regression in handling empty format specifiers after a colon (``{:}``)
|
||||||
|
(`#3086 <https://github.com/fmtlib/fmt/pull/3086>`_).
|
||||||
|
Thanks `@oxidase (Michael Krasnyk) <https://github.com/oxidase>`_.
|
||||||
|
|
||||||
|
* Worked around a broken implementation of ``std::is_constant_evaluated`` in
|
||||||
|
some versions of libstdc++ on clang
|
||||||
|
(`#3247 <https://github.com/fmtlib/fmt/issues/3247>`_,
|
||||||
|
`#3281 <https://github.com/fmtlib/fmt/pull/3281>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Fixed formatting of volatile variables
|
||||||
|
(`#3068 <https://github.com/fmtlib/fmt/pull/3068>`_).
|
||||||
|
|
||||||
|
* Fixed various warnings and compilation issues
|
||||||
|
(`#3057 <https://github.com/fmtlib/fmt/pull/3057>`_,
|
||||||
|
`#3066 <https://github.com/fmtlib/fmt/pull/3066>`_,
|
||||||
|
`#3072 <https://github.com/fmtlib/fmt/pull/3072>`_,
|
||||||
|
`#3082 <https://github.com/fmtlib/fmt/pull/3082>`_,
|
||||||
|
`#3091 <https://github.com/fmtlib/fmt/pull/3091>`_,
|
||||||
|
`#3092 <https://github.com/fmtlib/fmt/issues/3092>`_,
|
||||||
|
`#3093 <https://github.com/fmtlib/fmt/pull/3093>`_,
|
||||||
|
`#3095 <https://github.com/fmtlib/fmt/pull/3095>`_,
|
||||||
|
`#3096 <https://github.com/fmtlib/fmt/issues/3096>`_,
|
||||||
|
`#3097 <https://github.com/fmtlib/fmt/pull/3097>`_,
|
||||||
|
`#3128 <https://github.com/fmtlib/fmt/issues/3128>`_,
|
||||||
|
`#3129 <https://github.com/fmtlib/fmt/pull/3129>`_,
|
||||||
|
`#3137 <https://github.com/fmtlib/fmt/pull/3137>`_,
|
||||||
|
`#3139 <https://github.com/fmtlib/fmt/pull/3139>`_,
|
||||||
|
`#3140 <https://github.com/fmtlib/fmt/issues/3140>`_,
|
||||||
|
`#3142 <https://github.com/fmtlib/fmt/pull/3142>`_,
|
||||||
|
`#3149 <https://github.com/fmtlib/fmt/issues/3149>`_,
|
||||||
|
`#3150 <https://github.com/fmtlib/fmt/pull/3150>`_,
|
||||||
|
`#3154 <https://github.com/fmtlib/fmt/issues/3154>`_,
|
||||||
|
`#3163 <https://github.com/fmtlib/fmt/issues/3163>`_,
|
||||||
|
`#3178 <https://github.com/fmtlib/fmt/issues/3178>`_,
|
||||||
|
`#3184 <https://github.com/fmtlib/fmt/pull/3184>`_,
|
||||||
|
`#3196 <https://github.com/fmtlib/fmt/pull/3196>`_,
|
||||||
|
`#3204 <https://github.com/fmtlib/fmt/issues/3204>`_,
|
||||||
|
`#3206 <https://github.com/fmtlib/fmt/pull/3206>`_,
|
||||||
|
`#3208 <https://github.com/fmtlib/fmt/pull/3208>`_,
|
||||||
|
`#3213 <https://github.com/fmtlib/fmt/issues/3213>`_,
|
||||||
|
`#3216 <https://github.com/fmtlib/fmt/pull/3216>`_,
|
||||||
|
`#3224 <https://github.com/fmtlib/fmt/issues/3224>`_,
|
||||||
|
`#3226 <https://github.com/fmtlib/fmt/issues/3226>`_,
|
||||||
|
`#3228 <https://github.com/fmtlib/fmt/issues/3228>`_,
|
||||||
|
`#3229 <https://github.com/fmtlib/fmt/pull/3229>`_,
|
||||||
|
`#3259 <https://github.com/fmtlib/fmt/pull/3259>`_,
|
||||||
|
`#3274 <https://github.com/fmtlib/fmt/issues/3274>`_,
|
||||||
|
`#3287 <https://github.com/fmtlib/fmt/issues/3287>`_,
|
||||||
|
`#3288 <https://github.com/fmtlib/fmt/pull/3288>`_,
|
||||||
|
`#3292 <https://github.com/fmtlib/fmt/issues/3292>`_,
|
||||||
|
`#3295 <https://github.com/fmtlib/fmt/pull/3295>`_,
|
||||||
|
`#3296 <https://github.com/fmtlib/fmt/pull/3296>`_,
|
||||||
|
`#3298 <https://github.com/fmtlib/fmt/issues/3298>`_,
|
||||||
|
`#3325 <https://github.com/fmtlib/fmt/issues/3325>`_,
|
||||||
|
`#3326 <https://github.com/fmtlib/fmt/pull/3326>`_,
|
||||||
|
`#3334 <https://github.com/fmtlib/fmt/issues/3334>`_,
|
||||||
|
`#3342 <https://github.com/fmtlib/fmt/issues/3342>`_,
|
||||||
|
`#3343 <https://github.com/fmtlib/fmt/pull/3343>`_,
|
||||||
|
`#3351 <https://github.com/fmtlib/fmt/issues/3351>`_,
|
||||||
|
`#3352 <https://github.com/fmtlib/fmt/pull/3352>`_,
|
||||||
|
`#3362 <https://github.com/fmtlib/fmt/pull/3362>`_,
|
||||||
|
`#3365 <https://github.com/fmtlib/fmt/issues/3365>`_,
|
||||||
|
`#3366 <https://github.com/fmtlib/fmt/pull/3366>`_,
|
||||||
|
`#3374 <https://github.com/fmtlib/fmt/pull/3374>`_,
|
||||||
|
`#3377 <https://github.com/fmtlib/fmt/issues/3377>`_,
|
||||||
|
`#3378 <https://github.com/fmtlib/fmt/pull/3378>`_,
|
||||||
|
`#3381 <https://github.com/fmtlib/fmt/issues/3381>`_,
|
||||||
|
`#3398 <https://github.com/fmtlib/fmt/pull/3398>`_,
|
||||||
|
`#3413 <https://github.com/fmtlib/fmt/pull/3413>`_,
|
||||||
|
`#3415 <https://github.com/fmtlib/fmt/issues/3415>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
|
||||||
|
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
|
||||||
|
`@NewbieOrange <https://github.com/NewbieOrange>`_,
|
||||||
|
`@EngineLessCC (VivyaCC) <https://github.com/EngineLessCC>`_,
|
||||||
|
`@asmaloney (Andy Maloney) <https://github.com/asmaloney>`_,
|
||||||
|
`@HazardyKnusperkeks (Björn Schäpers)
|
||||||
|
<https://github.com/HazardyKnusperkeks>`_,
|
||||||
|
`@sergiud (Sergiu Deitsch) <https://github.com/sergiud>`_,
|
||||||
|
`@Youw (Ihor Dutchak) <https://github.com/Youw>`_,
|
||||||
|
`@thesmurph <https://github.com/thesmurph>`_,
|
||||||
|
`@czudziakm (Maksymilian Czudziak) <https://github.com/czudziakm>`_,
|
||||||
|
`@Roman-Koshelev <https://github.com/Roman-Koshelev>`_,
|
||||||
|
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||||
|
`@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_,
|
||||||
|
`@russelltg (Russell Greene) <https://github.com/russelltg>`_,
|
||||||
|
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
|
||||||
|
`@tmartin-gh <https://github.com/tmartin-gh>`_,
|
||||||
|
`@Zhaojun-Liu (June Liu) <https://github.com/Zhaojun-Liu>`_,
|
||||||
|
`@louiswins (Louis Wilson) <https://github.com/louiswins>`_,
|
||||||
|
`@mogemimi <https://github.com/mogemimi>`_.
|
||||||
|
|
||||||
|
9.1.0 - 2022-08-27
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* ``fmt::formatted_size`` now works at compile time
|
||||||
|
(`#3026 <https://github.com/fmtlib/fmt/pull/3026>`_). For example
|
||||||
|
(`godbolt <https://godbolt.org/z/1MW5rMdf8>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/compile.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace fmt::literals;
|
||||||
|
constexpr size_t n = fmt::formatted_size("{}"_cf, 42);
|
||||||
|
fmt::print("{}\n", n); // prints 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@marksantaniello (Mark Santaniello)
|
||||||
|
<https://github.com/marksantaniello>`_.
|
||||||
|
|
||||||
|
* Fixed handling of invalid UTF-8
|
||||||
|
(`#3038 <https://github.com/fmtlib/fmt/pull/3038>`_,
|
||||||
|
`#3044 <https://github.com/fmtlib/fmt/pull/3044>`_,
|
||||||
|
`#3056 <https://github.com/fmtlib/fmt/pull/3056>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
|
||||||
|
`@skeeto (Christopher Wellons) <https://github.com/skeeto>`_.
|
||||||
|
|
||||||
|
* Improved Unicode support in ``ostream`` overloads of ``print``
|
||||||
|
(`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_,
|
||||||
|
`#3001 <https://github.com/fmtlib/fmt/pull/3001>`_,
|
||||||
|
`#3025 <https://github.com/fmtlib/fmt/pull/3025>`_).
|
||||||
|
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_.
|
||||||
|
|
||||||
|
* Fixed handling of the sign specifier in localized formatting on systems with
|
||||||
|
32-bit ``wchar_t`` (`#3041 <https://github.com/fmtlib/fmt/issues/3041>`_).
|
||||||
|
|
||||||
|
* Added support for wide streams to ``fmt::streamed``
|
||||||
|
(`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Added the ``n`` specifier that disables the output of delimiters when
|
||||||
|
formatting ranges (`#2981 <https://github.com/fmtlib/fmt/pull/2981>`_,
|
||||||
|
`#2983 <https://github.com/fmtlib/fmt/pull/2983>`_).
|
||||||
|
For example (`godbolt <https://godbolt.org/z/roKqGdj8c>`__):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/ranges.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto v = std::vector{1, 2, 3};
|
||||||
|
fmt::print("{:n}\n", v); // prints 1, 2, 3
|
||||||
|
}
|
||||||
|
|
||||||
|
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||||
|
|
||||||
|
* Worked around problematic ``std::string_view`` constructors introduced in
|
||||||
|
C++23 (`#3030 <https://github.com/fmtlib/fmt/issues/3030>`_,
|
||||||
|
`#3050 <https://github.com/fmtlib/fmt/issues/3050>`_).
|
||||||
|
Thanks `@strega-nil-ms (nicole mazzuca) <https://github.com/strega-nil-ms>`_.
|
||||||
|
|
||||||
|
* Improve handling (exclusion) of recursive ranges
|
||||||
|
(`#2968 <https://github.com/fmtlib/fmt/issues/2968>`_,
|
||||||
|
`#2974 <https://github.com/fmtlib/fmt/pull/2974>`_).
|
||||||
|
Thanks `@Dani-Hub (Daniel Krügler) <https://github.com/Dani-Hub>`_.
|
||||||
|
|
||||||
|
* Improved error reporting in format string compilation
|
||||||
|
(`#3055 <https://github.com/fmtlib/fmt/issues/3055>`_).
|
||||||
|
|
||||||
|
* Improved the implementation of
|
||||||
|
`Dragonbox <https://github.com/jk-jeon/dragonbox>`_, the algorithm used for
|
||||||
|
the default floating-point formatting
|
||||||
|
(`#2984 <https://github.com/fmtlib/fmt/pull/2984>`_).
|
||||||
|
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
|
||||||
|
|
||||||
|
* Fixed issues with floating-point formatting on exotic platforms.
|
||||||
|
|
||||||
|
* Improved the implementation of chrono formatting
|
||||||
|
(`#3010 <https://github.com/fmtlib/fmt/pull/3010>`_).
|
||||||
|
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
|
* Improved documentation
|
||||||
|
(`#2966 <https://github.com/fmtlib/fmt/pull/2966>`_,
|
||||||
|
`#3009 <https://github.com/fmtlib/fmt/pull/3009>`_,
|
||||||
|
`#3020 <https://github.com/fmtlib/fmt/issues/3020>`_,
|
||||||
|
`#3037 <https://github.com/fmtlib/fmt/pull/3037>`_).
|
||||||
|
Thanks `@mwinterb <https://github.com/mwinterb>`_,
|
||||||
|
`@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_
|
||||||
|
and `@remiburtin (Rémi Burtin) <https://github.com/remiburtin>`_.
|
||||||
|
|
||||||
|
* Improved build configuration
|
||||||
|
(`#2991 <https://github.com/fmtlib/fmt/pull/2991>`_,
|
||||||
|
`#2995 <https://github.com/fmtlib/fmt/pull/2995>`_,
|
||||||
|
`#3004 <https://github.com/fmtlib/fmt/issues/3004>`_,
|
||||||
|
`#3007 <https://github.com/fmtlib/fmt/pull/3007>`_,
|
||||||
|
`#3040 <https://github.com/fmtlib/fmt/pull/3040>`_).
|
||||||
|
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_ and
|
||||||
|
`@hwhsu1231 (Haowei Hsu) <https://github.com/hwhsu1231>`_.
|
||||||
|
|
||||||
|
* Fixed various warnings and compilation issues
|
||||||
|
(`#2969 <https://github.com/fmtlib/fmt/issues/2969>`_,
|
||||||
|
`#2971 <https://github.com/fmtlib/fmt/pull/2971>`_,
|
||||||
|
`#2975 <https://github.com/fmtlib/fmt/issues/2975>`_,
|
||||||
|
`#2982 <https://github.com/fmtlib/fmt/pull/2982>`_,
|
||||||
|
`#2985 <https://github.com/fmtlib/fmt/pull/2985>`_,
|
||||||
|
`#2988 <https://github.com/fmtlib/fmt/issues/2988>`_,
|
||||||
|
`#2989 <https://github.com/fmtlib/fmt/issues/2989>`_,
|
||||||
|
`#3000 <https://github.com/fmtlib/fmt/issues/3000>`_,
|
||||||
|
`#3006 <https://github.com/fmtlib/fmt/issues/3006>`_,
|
||||||
|
`#3014 <https://github.com/fmtlib/fmt/issues/3014>`_,
|
||||||
|
`#3015 <https://github.com/fmtlib/fmt/issues/3015>`_,
|
||||||
|
`#3021 <https://github.com/fmtlib/fmt/pull/3021>`_,
|
||||||
|
`#3023 <https://github.com/fmtlib/fmt/issues/3023>`_,
|
||||||
|
`#3024 <https://github.com/fmtlib/fmt/pull/3024>`_,
|
||||||
|
`#3029 <https://github.com/fmtlib/fmt/pull/3029>`_,
|
||||||
|
`#3043 <https://github.com/fmtlib/fmt/pull/3043>`_,
|
||||||
|
`#3052 <https://github.com/fmtlib/fmt/issues/3052>`_,
|
||||||
|
`#3053 <https://github.com/fmtlib/fmt/pull/3053>`_,
|
||||||
|
`#3054 <https://github.com/fmtlib/fmt/pull/3054>`_).
|
||||||
|
Thanks `@h-friederich (Hannes Friederich) <https://github.com/h-friederich>`_,
|
||||||
|
`@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
|
||||||
|
`@olupton (Olli Lupton) <https://github.com/olupton>`_,
|
||||||
|
`@bernhardmgruber (Bernhard Manfred Gruber)
|
||||||
|
<https://github.com/bernhardmgruber>`_,
|
||||||
|
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||||
|
|
||||||
9.0.0 - 2022-07-04
|
9.0.0 - 2022-07-04
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -19,7 +812,7 @@
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr auto answer = compile_time_itoa(0.42);
|
constexpr auto answer = compile_time_dtoa(0.42);
|
||||||
|
|
||||||
works with the default settings.
|
works with the default settings.
|
||||||
|
|
||||||
|
@ -2134,7 +2927,7 @@
|
||||||
<https://github.com/kwesolowski>`_.
|
<https://github.com/kwesolowski>`_.
|
||||||
|
|
||||||
* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
|
* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
|
||||||
macro to prevent interferring with fuzzing of projects using {fmt}
|
macro to prevent interfering with fuzzing of projects using {fmt}
|
||||||
(`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
|
(`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
|
||||||
Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.
|
Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2012 - present, Victor Zverovich
|
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|
73
README.rst
73
README.rst
|
@ -12,9 +12,6 @@
|
||||||
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
||||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
||||||
|
|
||||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true
|
|
||||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
|
||||||
|
|
||||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
||||||
:alt: fmt is continuously fuzzed at oss-fuzz
|
:alt: fmt is continuously fuzzed at oss-fuzz
|
||||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
|
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
|
||||||
|
@ -25,6 +22,9 @@
|
||||||
:alt: Ask questions at StackOverflow with the tag fmt
|
:alt: Ask questions at StackOverflow with the tag fmt
|
||||||
:target: https://stackoverflow.com/questions/tagged/fmt
|
:target: https://stackoverflow.com/questions/tagged/fmt
|
||||||
|
|
||||||
|
.. image:: https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge
|
||||||
|
:target: https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt
|
||||||
|
|
||||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||||
alternative to C stdio and C++ iostreams.
|
alternative to C stdio and C++ iostreams.
|
||||||
|
|
||||||
|
@ -50,7 +50,9 @@ Features
|
||||||
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
||||||
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||||
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
|
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
|
||||||
round-trip guarantees
|
round-trip guarantees using the `Dragonbox <https://github.com/jk-jeon/dragonbox>`_
|
||||||
|
algorithm
|
||||||
|
* Portable Unicode support
|
||||||
* Safe `printf implementation
|
* Safe `printf implementation
|
||||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
||||||
extension for positional arguments
|
extension for positional arguments
|
||||||
|
@ -67,7 +69,7 @@ Features
|
||||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
|
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
|
||||||
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
|
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
|
||||||
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
|
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
|
||||||
* Safety: the library is fully type safe, errors in format strings can be
|
* Safety: the library is fully type-safe, errors in format strings can be
|
||||||
reported at compile time, automatic memory management prevents buffer overflow
|
reported at compile time, automatic memory management prevents buffer overflow
|
||||||
errors
|
errors
|
||||||
* Ease of use: small self-contained code base, no external dependencies,
|
* Ease of use: small self-contained code base, no external dependencies,
|
||||||
|
@ -77,7 +79,7 @@ Features
|
||||||
consistent output across platforms and support for older compilers
|
consistent output across platforms and support for older compilers
|
||||||
* Clean warning-free codebase even on high warning levels such as
|
* Clean warning-free codebase even on high warning levels such as
|
||||||
``-Wall -Wextra -pedantic``
|
``-Wall -Wextra -pedantic``
|
||||||
* Locale-independence by default
|
* Locale independence by default
|
||||||
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
|
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
|
||||||
|
|
||||||
See the `documentation <https://fmt.dev>`_ for more details.
|
See the `documentation <https://fmt.dev>`_ for more details.
|
||||||
|
@ -194,24 +196,24 @@ Speed tests
|
||||||
================= ============= ===========
|
================= ============= ===========
|
||||||
Library Method Run Time, s
|
Library Method Run Time, s
|
||||||
================= ============= ===========
|
================= ============= ===========
|
||||||
libc printf 1.04
|
libc printf 0.91
|
||||||
libc++ std::ostream 3.05
|
libc++ std::ostream 2.49
|
||||||
{fmt} 6.1.1 fmt::print 0.75
|
{fmt} 9.1 fmt::print 0.74
|
||||||
Boost Format 1.67 boost::format 7.24
|
Boost Format 1.80 boost::format 6.26
|
||||||
Folly Format folly::format 2.23
|
Folly Format folly::format 1.87
|
||||||
================= ============= ===========
|
================= ============= ===========
|
||||||
|
|
||||||
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``.
|
||||||
|
|
||||||
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||||
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
12.6.1 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
||||||
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||||
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
||||||
further details refer to the `source
|
further details refer to the `source
|
||||||
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
|
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
|
||||||
|
|
||||||
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
|
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
|
||||||
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||||
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
||||||
`ryu <https://github.com/ulfjack/ryu>`_:
|
`ryu <https://github.com/ulfjack/ryu>`_:
|
||||||
|
|
||||||
|
@ -227,7 +229,7 @@ The script `bloat-test.py
|
||||||
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
||||||
tests compile time and code bloat for nontrivial projects.
|
tests compile time and code bloat for nontrivial projects.
|
||||||
It generates 100 translation units and uses ``printf()`` or its alternative
|
It generates 100 translation units and uses ``printf()`` or its alternative
|
||||||
five times in each to simulate a medium sized project. The resulting
|
five times in each to simulate a medium-sized project. The resulting
|
||||||
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
||||||
macOS Sierra, best of three) is shown in the following tables.
|
macOS Sierra, best of three) is shown in the following tables.
|
||||||
|
|
||||||
|
@ -248,7 +250,7 @@ As you can see, {fmt} has 60% less overhead in terms of resulting binary code
|
||||||
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
||||||
and Folly Format have the largest overheads.
|
and Folly Format have the largest overheads.
|
||||||
|
|
||||||
``printf+string`` is the same as ``printf`` but with extra ``<string>``
|
``printf+string`` is the same as ``printf`` but with an extra ``<string>``
|
||||||
include to measure the overhead of the latter.
|
include to measure the overhead of the latter.
|
||||||
|
|
||||||
**Non-optimized build**
|
**Non-optimized build**
|
||||||
|
@ -264,14 +266,14 @@ Boost Format 54.1 365 303
|
||||||
Folly Format 79.9 445 430
|
Folly Format 79.9 445 430
|
||||||
============= =============== ==================== ==================
|
============= =============== ==================== ==================
|
||||||
|
|
||||||
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
``libc``, ``lib(std)c++``, and ``libfmt`` are all linked as shared libraries to
|
||||||
compare formatting function overhead only. Boost Format is a
|
compare formatting function overhead only. Boost Format is a
|
||||||
header-only library so it doesn't provide any linkage options.
|
header-only library so it doesn't provide any linkage options.
|
||||||
|
|
||||||
Running the tests
|
Running the tests
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Please refer to `Building the library`__ for the instructions on how to build
|
Please refer to `Building the library`__ for instructions on how to build
|
||||||
the library and run the unit tests.
|
the library and run the unit tests.
|
||||||
|
|
||||||
__ https://fmt.dev/latest/usage.html#building-the-library
|
__ https://fmt.dev/latest/usage.html#building-the-library
|
||||||
|
@ -296,9 +298,12 @@ or the bloat test::
|
||||||
Migrating code
|
Migrating code
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
|
`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`_ v17 (not yet
|
||||||
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
|
released) provides the `modernize-use-std-print
|
||||||
``fmt::print``.
|
<https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html>`_
|
||||||
|
check that is capable of converting occurrences of ``printf`` and
|
||||||
|
``fprintf`` to ``fmt::print`` if configured to do so. (By default it
|
||||||
|
converts to ``std::print``.)
|
||||||
|
|
||||||
Projects using this library
|
Projects using this library
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -306,8 +311,6 @@ Projects using this library
|
||||||
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
|
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
|
||||||
real-time strategy game
|
real-time strategy game
|
||||||
|
|
||||||
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
|
|
||||||
|
|
||||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||||
an open-source library for mathematical programming
|
an open-source library for mathematical programming
|
||||||
|
|
||||||
|
@ -325,8 +328,10 @@ Projects using this library
|
||||||
|
|
||||||
* `ccache <https://ccache.dev/>`_: a compiler cache
|
* `ccache <https://ccache.dev/>`_: a compiler cache
|
||||||
|
|
||||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
|
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: an analytical database
|
||||||
management system
|
management system
|
||||||
|
|
||||||
|
* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator
|
||||||
|
|
||||||
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||||
vehicle
|
vehicle
|
||||||
|
@ -363,6 +368,10 @@ Projects using this library
|
||||||
|
|
||||||
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
||||||
|
|
||||||
|
* `libunicode <https://github.com/contour-terminal/libunicode/>`_: a modern C++17 Unicode library
|
||||||
|
|
||||||
|
* `MariaDB <https://mariadb.org/>`_: relational database management system
|
||||||
|
|
||||||
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
||||||
research programming language for concurrent ownership
|
research programming language for concurrent ownership
|
||||||
|
|
||||||
|
@ -392,7 +401,7 @@ Projects using this library
|
||||||
proxy
|
proxy
|
||||||
|
|
||||||
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
|
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
|
||||||
for mission critical systems written in C++
|
for mission-critical systems written in C++
|
||||||
|
|
||||||
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
|
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
|
||||||
library
|
library
|
||||||
|
@ -416,6 +425,9 @@ Projects using this library
|
||||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
||||||
MMORPG framework
|
MMORPG framework
|
||||||
|
|
||||||
|
* `🐙 userver framework <https://userver.tech/>`_: open-source asynchronous
|
||||||
|
framework with a rich set of abstractions and database drivers
|
||||||
|
|
||||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
||||||
terminal
|
terminal
|
||||||
|
|
||||||
|
@ -473,7 +485,7 @@ error handling is awkward.
|
||||||
Boost Format
|
Boost Format
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
This is a very powerful library which supports both ``printf``-like format
|
This is a very powerful library that supports both ``printf``-like format
|
||||||
strings and positional arguments. Its main drawback is performance. According to
|
strings and positional arguments. Its main drawback is performance. According to
|
||||||
various benchmarks, it is much slower than other methods considered here. Boost
|
various benchmarks, it is much slower than other methods considered here. Boost
|
||||||
Format also has excessive build times and severe code bloat issues (see
|
Format also has excessive build times and severe code bloat issues (see
|
||||||
|
@ -482,7 +494,7 @@ Format also has excessive build times and severe code bloat issues (see
|
||||||
FastFormat
|
FastFormat
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
This is an interesting library which is fast, safe and has positional arguments.
|
This is an interesting library that is fast, safe, and has positional arguments.
|
||||||
However, it has significant limitations, citing its author:
|
However, it has significant limitations, citing its author:
|
||||||
|
|
||||||
Three features that have no hope of being accommodated within the
|
Three features that have no hope of being accommodated within the
|
||||||
|
@ -498,7 +510,7 @@ restrictive for using it in some projects.
|
||||||
Boost Spirit.Karma
|
Boost Spirit.Karma
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This is not really a formatting library but I decided to include it here for
|
This is not a formatting library but I decided to include it here for
|
||||||
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
||||||
with arguments. The library is pretty fast, but slower on integer formatting
|
with arguments. The library is pretty fast, but slower on integer formatting
|
||||||
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
|
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
|
||||||
|
@ -517,7 +529,7 @@ Documentation License
|
||||||
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
|
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
|
||||||
section in the documentation is based on the one from Python `string module
|
section in the documentation is based on the one from Python `string module
|
||||||
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
|
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
|
||||||
For this reason the documentation is distributed under the Python Software
|
For this reason, the documentation is distributed under the Python Software
|
||||||
Foundation license available in `doc/python-license.txt
|
Foundation license available in `doc/python-license.txt
|
||||||
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||||
It only applies if you distribute the documentation of {fmt}.
|
It only applies if you distribute the documentation of {fmt}.
|
||||||
|
@ -526,8 +538,7 @@ Maintainers
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
||||||
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
|
<https://github.com/vitaut>`_) with contributions from many other people.
|
||||||
<https://github.com/foonathan>`_) with contributions from many other people.
|
|
||||||
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
||||||
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
|
`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
|
Let us know if your contribution is not listed or mentioned incorrectly and
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
find_program(DOXYGEN doxygen)
|
find_program(DOXYGEN doxygen
|
||||||
|
PATHS "$ENV{ProgramFiles}/doxygen/bin"
|
||||||
|
"$ENV{ProgramFiles\(x86\)}/doxygen/bin")
|
||||||
if (NOT DOXYGEN)
|
if (NOT DOXYGEN)
|
||||||
message(STATUS "Target 'doc' disabled (requires doxygen)")
|
message(STATUS "Target 'doc' disabled (requires doxygen)")
|
||||||
return ()
|
return ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
find_package(PythonInterp QUIET REQUIRED)
|
# Find the Python interpreter and set the PYTHON_EXECUTABLE variable.
|
||||||
|
if (CMAKE_VERSION VERSION_LESS 3.12)
|
||||||
|
# This logic is deprecated in CMake after 3.12.
|
||||||
|
find_package(PythonInterp QUIET REQUIRED)
|
||||||
|
else ()
|
||||||
|
find_package(Python QUIET REQUIRED)
|
||||||
|
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
|
||||||
|
endif ()
|
||||||
|
|
||||||
add_custom_target(doc
|
add_custom_target(doc
|
||||||
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
|
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
|
||||||
|
|
356
doc/api.rst
356
doc/api.rst
|
@ -7,7 +7,7 @@ API Reference
|
||||||
The {fmt} library API consists of the following parts:
|
The {fmt} library API consists of the following parts:
|
||||||
|
|
||||||
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
|
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
|
||||||
for ``char``/UTF-8 with compile-time checks and minimal dependencies
|
for ``char``/UTF-8 with C++20 compile-time checks and minimal dependencies
|
||||||
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
|
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
|
||||||
formatting functions and locale support
|
formatting functions and locale support
|
||||||
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
||||||
|
@ -17,6 +17,7 @@ The {fmt} library API consists of the following parts:
|
||||||
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
||||||
* :ref:`fmt/os.h <os-api>`: system APIs
|
* :ref:`fmt/os.h <os-api>`: system APIs
|
||||||
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||||
|
* :ref:`fmt/args.h <args-api>`: dynamic argument lists
|
||||||
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||||
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
|
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
|
||||||
|
|
||||||
|
@ -28,10 +29,11 @@ macros have prefix ``FMT_``.
|
||||||
Core API
|
Core API
|
||||||
========
|
========
|
||||||
|
|
||||||
``fmt/core.h`` defines the core API which provides main formatting functions for
|
``fmt/core.h`` defines the core API which provides main formatting functions
|
||||||
``char``/UTF-8 with compile-time checks. It has minimal include dependencies for
|
for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
|
||||||
better compile times. This header is only beneficial when using {fmt} as a
|
dependencies for better compile times. This header is only beneficial when
|
||||||
library and not in the header-only mode.
|
using {fmt} as a library (the default) and not in the header-only mode.
|
||||||
|
It also provides ``formatter`` specializations for built-in and string types.
|
||||||
|
|
||||||
The following functions use :ref:`format string syntax <syntax>`
|
The following functions use :ref:`format string syntax <syntax>`
|
||||||
similar to that of Python's `str.format
|
similar to that of Python's `str.format
|
||||||
|
@ -70,113 +72,18 @@ checked at compile time in C++20. To pass a runtime format string wrap it in
|
||||||
Compile-Time Format String Checks
|
Compile-Time Format String Checks
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in
|
Compile-time format string checks are enabled by default on compilers
|
||||||
and string types as well as user-defined types with ``constexpr`` ``parse``
|
that support C++20 ``consteval``. On older compilers you can use the
|
||||||
functions in their ``formatter`` specializations.
|
:ref:`FMT_STRING <legacy-checks>`: macro defined in ``fmt/format.h`` instead.
|
||||||
Requires C++14 and is a no-op in C++11.
|
|
||||||
|
|
||||||
.. doxygendefine:: FMT_STRING
|
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
|
||||||
|
|
||||||
To force the use of compile-time checks, define the preprocessor variable
|
|
||||||
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
|
||||||
will fail to compile with regular strings. Runtime-checked
|
|
||||||
formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc.
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_string
|
.. doxygenclass:: fmt::basic_format_string
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. doxygentypedef:: fmt::format_string
|
.. doxygentypedef:: fmt::format_string
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::runtime(const S&)
|
.. doxygenfunction:: fmt::runtime(string_view) -> runtime_format_string<>
|
||||||
|
|
||||||
Named Arguments
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::arg(const S&, const T&)
|
|
||||||
|
|
||||||
Named arguments are not supported in compile-time checks at the moment.
|
|
||||||
|
|
||||||
Argument Lists
|
|
||||||
--------------
|
|
||||||
|
|
||||||
You can create your own formatting function with compile-time checks and small
|
|
||||||
binary footprint, for example (https://godbolt.org/z/oba4Mc):
|
|
||||||
|
|
||||||
.. code:: c++
|
|
||||||
|
|
||||||
#include <fmt/format.h>
|
|
||||||
|
|
||||||
void vlog(const char* file, int line, fmt::string_view format,
|
|
||||||
fmt::format_args args) {
|
|
||||||
fmt::print("{}: {}: ", file, line);
|
|
||||||
fmt::vprint(format, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename... Args>
|
|
||||||
void log(const char* file, int line, const S& format, Args&&... args) {
|
|
||||||
vlog(file, line, format, fmt::make_format_args(args...));
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MY_LOG(format, ...) \
|
|
||||||
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
|
|
||||||
|
|
||||||
MY_LOG("invalid squishiness: {}", 42);
|
|
||||||
|
|
||||||
Note that ``vlog`` is not parameterized on argument types which improves compile
|
|
||||||
times and reduces binary code size compared to a fully parameterized version.
|
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::format_arg_store
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::dynamic_format_arg_store
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_args
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygentypedef:: fmt::format_args
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_arg
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_parse_context
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_context
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygentypedef:: fmt::format_context
|
|
||||||
|
|
||||||
Compatibility
|
|
||||||
-------------
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_string_view
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygentypedef:: fmt::string_view
|
|
||||||
|
|
||||||
Locale
|
|
||||||
------
|
|
||||||
|
|
||||||
All formatting is locale-independent by default. Use the ``'L'`` format
|
|
||||||
specifier to insert the appropriate number separator characters from the
|
|
||||||
locale::
|
|
||||||
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <locale>
|
|
||||||
|
|
||||||
std::locale::global(std::locale("en_US.UTF-8"));
|
|
||||||
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
|
|
||||||
|
|
||||||
.. _format-api:
|
|
||||||
|
|
||||||
Format API
|
|
||||||
==========
|
|
||||||
|
|
||||||
``fmt/format.h`` defines the full format API providing additional formatting
|
|
||||||
functions and locale support.
|
|
||||||
|
|
||||||
.. _udt:
|
.. _udt:
|
||||||
|
|
||||||
|
@ -186,14 +93,39 @@ Formatting User-Defined Types
|
||||||
The {fmt} library provides formatters for many standard C++ types.
|
The {fmt} library provides formatters for many standard C++ types.
|
||||||
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
|
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
|
||||||
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
|
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
|
||||||
and time formatting and :ref:`fmt/std.h <std-api>` for path and variant
|
and time formatting and :ref:`fmt/std.h <std-api>` for other standard library
|
||||||
formatting.
|
types.
|
||||||
|
|
||||||
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
|
There are two ways to make a user-defined type formattable: providing a
|
||||||
template and implement ``parse`` and ``format`` methods::
|
``format_as`` function or specializing the ``formatter`` struct template.
|
||||||
|
|
||||||
|
Use ``format_as`` if you want to make your type formattable as some other type
|
||||||
|
with the same format specifiers. The ``format_as`` function should take an
|
||||||
|
object of your type and return an object of a formattable type. It should be
|
||||||
|
defined in the same namespace as your type.
|
||||||
|
|
||||||
|
Example (https://godbolt.org/z/r7vvGE1v7)::
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
namespace kevin_namespacy {
|
||||||
|
enum class film {
|
||||||
|
house_of_cards, american_beauty, se7en = 7
|
||||||
|
};
|
||||||
|
auto format_as(film f) { return fmt::underlying(f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
|
||||||
|
}
|
||||||
|
|
||||||
|
Using the specialization API is more complex but gives you full control over
|
||||||
|
parsing and formatting. To use this method specialize the ``formatter`` struct
|
||||||
|
template for your type and implement ``parse`` and ``format`` methods.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
struct point {
|
struct point {
|
||||||
double x, y;
|
double x, y;
|
||||||
};
|
};
|
||||||
|
@ -203,7 +135,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||||
char presentation = 'f';
|
char presentation = 'f';
|
||||||
|
|
||||||
// Parses format specifications of the form ['f' | 'e'].
|
// Parses format specifications of the form ['f' | 'e'].
|
||||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator {
|
||||||
// [ctx.begin(), ctx.end()) is a character range that contains a part of
|
// [ctx.begin(), ctx.end()) is a character range that contains a part of
|
||||||
// the format string starting from the format specifications to be parsed,
|
// the format string starting from the format specifications to be parsed,
|
||||||
// e.g. in
|
// e.g. in
|
||||||
|
@ -224,7 +156,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||||
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
|
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
|
||||||
|
|
||||||
// Check if reached the end of the range:
|
// Check if reached the end of the range:
|
||||||
if (it != end && *it != '}') throw format_error("invalid format");
|
if (it != end && *it != '}') throw_format_error("invalid format");
|
||||||
|
|
||||||
// Return an iterator past the end of the parsed range:
|
// Return an iterator past the end of the parsed range:
|
||||||
return it;
|
return it;
|
||||||
|
@ -232,8 +164,7 @@ template and implement ``parse`` and ``format`` methods::
|
||||||
|
|
||||||
// Formats the point p using the parsed format specification (presentation)
|
// Formats the point p using the parsed format specification (presentation)
|
||||||
// stored in this formatter.
|
// stored in this formatter.
|
||||||
template <typename FormatContext>
|
auto format(const point& p, format_context& ctx) const -> format_context::iterator {
|
||||||
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
|
|
||||||
// ctx.out() is an output iterator to write to.
|
// ctx.out() is an output iterator to write to.
|
||||||
return presentation == 'f'
|
return presentation == 'f'
|
||||||
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
|
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
|
||||||
|
@ -250,22 +181,33 @@ Then you can pass objects of type ``point`` to any formatting function::
|
||||||
You can also reuse existing formatters via inheritance or composition, for
|
You can also reuse existing formatters via inheritance or composition, for
|
||||||
example::
|
example::
|
||||||
|
|
||||||
|
// color.h:
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
enum class color {red, green, blue};
|
enum class color {red, green, blue};
|
||||||
|
|
||||||
template <> struct fmt::formatter<color>: formatter<string_view> {
|
template <> struct fmt::formatter<color>: formatter<string_view> {
|
||||||
// parse is inherited from formatter<string_view>.
|
// parse is inherited from formatter<string_view>.
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(color c, FormatContext& ctx) const {
|
auto format(color c, format_context& ctx) const;
|
||||||
string_view name = "unknown";
|
|
||||||
switch (c) {
|
|
||||||
case color::red: name = "red"; break;
|
|
||||||
case color::green: name = "green"; break;
|
|
||||||
case color::blue: name = "blue"; break;
|
|
||||||
}
|
|
||||||
return formatter<string_view>::format(name, ctx);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// color.cc:
|
||||||
|
#include "color.h"
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
auto fmt::formatter<color>::format(color c, format_context& ctx) const {
|
||||||
|
string_view name = "unknown";
|
||||||
|
switch (c) {
|
||||||
|
case color::red: name = "red"; break;
|
||||||
|
case color::green: name = "green"; break;
|
||||||
|
case color::blue: name = "blue"; break;
|
||||||
|
}
|
||||||
|
return formatter<string_view>::format(name, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that ``formatter<string_view>::format`` is defined in ``fmt/format.h`` so
|
||||||
|
it has to be included in the source file.
|
||||||
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
|
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
|
||||||
all string format specifications, for example
|
all string format specifications, for example
|
||||||
|
|
||||||
|
@ -277,8 +219,9 @@ will return ``" blue"``.
|
||||||
|
|
||||||
You can also write a formatter for a hierarchy of classes::
|
You can also write a formatter for a hierarchy of classes::
|
||||||
|
|
||||||
|
// demo.h:
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <fmt/format.h>
|
#include <fmt/core.h>
|
||||||
|
|
||||||
struct A {
|
struct A {
|
||||||
virtual ~A() {}
|
virtual ~A() {}
|
||||||
|
@ -292,41 +235,106 @@ You can also write a formatter for a hierarchy of classes::
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
|
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
|
||||||
fmt::formatter<std::string> {
|
fmt::formatter<std::string> {
|
||||||
template <typename FormatCtx>
|
auto format(const A& a, format_context& ctx) const {
|
||||||
auto format(const A& a, FormatCtx& ctx) const {
|
|
||||||
return fmt::formatter<std::string>::format(a.name(), ctx);
|
return fmt::formatter<std::string>::format(a.name(), ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// demo.cc:
|
||||||
|
#include "demo.h"
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
B b;
|
B b;
|
||||||
A& a = b;
|
A& a = b;
|
||||||
fmt::print("{}", a); // prints "B"
|
fmt::print("{}", a); // prints "B"
|
||||||
}
|
}
|
||||||
|
|
||||||
If a type provides both a ``formatter`` specialization and an implicit
|
Providing both a ``formatter`` specialization and a ``format_as`` overload is
|
||||||
conversion to a formattable type, the specialization takes precedence over the
|
disallowed.
|
||||||
conversion.
|
|
||||||
|
|
||||||
For enums {fmt} also provides the ``format_as`` extension API. To format an enum
|
Named Arguments
|
||||||
via this API define ``format_as`` that takes this enum and converts it to the
|
---------------
|
||||||
underlying type. ``format_as`` should be defined in the same namespace as the
|
|
||||||
enum.
|
|
||||||
|
|
||||||
Example (https://godbolt.org/z/r7vvGE1v7)::
|
.. doxygenfunction:: fmt::arg(const S&, const T&)
|
||||||
|
|
||||||
#include <fmt/format.h>
|
Named arguments are not supported in compile-time checks at the moment.
|
||||||
|
|
||||||
namespace kevin_namespacy {
|
Argument Lists
|
||||||
enum class film {
|
--------------
|
||||||
house_of_cards, american_beauty, se7en = 7
|
|
||||||
};
|
|
||||||
auto format_as(film f) { return fmt::underlying(f); }
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
You can create your own formatting function with compile-time checks and small
|
||||||
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
|
binary footprint, for example (https://godbolt.org/z/vajfWEG4b):
|
||||||
}
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
void vlog(const char* file, int line, fmt::string_view format,
|
||||||
|
fmt::format_args args) {
|
||||||
|
fmt::print("{}: {}: ", file, line);
|
||||||
|
fmt::vprint(format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
void log(const char* file, int line, fmt::format_string<T...> format, T&&... args) {
|
||||||
|
vlog(file, line, format, fmt::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MY_LOG(format, ...) log(__FILE__, __LINE__, format, __VA_ARGS__)
|
||||||
|
|
||||||
|
MY_LOG("invalid squishiness: {}", 42);
|
||||||
|
|
||||||
|
Note that ``vlog`` is not parameterized on argument types which improves compile
|
||||||
|
times and reduces binary code size compared to a fully parameterized version.
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::format_arg_store
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_args
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::format_args
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_arg
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_parse_context
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_context
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::format_context
|
||||||
|
|
||||||
|
.. _args-api:
|
||||||
|
|
||||||
|
Dynamic Argument Lists
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The header ``fmt/args.h`` provides ``dynamic_format_arg_store``, a builder-like
|
||||||
|
API that can be used to construct format argument lists dynamically.
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::dynamic_format_arg_store
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Compatibility
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_string_view
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::string_view
|
||||||
|
|
||||||
|
.. _format-api:
|
||||||
|
|
||||||
|
Format API
|
||||||
|
==========
|
||||||
|
|
||||||
|
``fmt/format.h`` defines the full format API providing additional formatting
|
||||||
|
functions and locale support.
|
||||||
|
|
||||||
Literal-Based API
|
Literal-Based API
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -339,7 +347,7 @@ Utilities
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::ptr(T p) -> const void*
|
.. doxygenfunction:: fmt::ptr(T p) -> const void*
|
||||||
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
|
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T, Deleter> &p) -> const void*
|
||||||
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
|
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type
|
.. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type
|
||||||
|
@ -405,6 +413,41 @@ normally don't do any allocations for built-in and string types except for
|
||||||
non-default floating-point formatting that occasionally falls back on
|
non-default floating-point formatting that occasionally falls back on
|
||||||
``sprintf``.
|
``sprintf``.
|
||||||
|
|
||||||
|
Locale
|
||||||
|
------
|
||||||
|
|
||||||
|
All formatting is locale-independent by default. Use the ``'L'`` format
|
||||||
|
specifier to insert the appropriate number separator characters from the
|
||||||
|
locale::
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
std::locale::global(std::locale("en_US.UTF-8"));
|
||||||
|
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
|
||||||
|
|
||||||
|
``fmt/format.h`` provides the following overloads of formatting functions that
|
||||||
|
take ``std::locale`` as a parameter. The locale type is a template parameter to
|
||||||
|
avoid the expensive ``<locale>`` include.
|
||||||
|
|
||||||
|
.. doxygenfunction:: format(const Locale& loc, format_string<T...> fmt, T&&... args) -> std::string
|
||||||
|
.. doxygenfunction:: format_to(OutputIt out, const Locale& loc, format_string<T...> fmt, T&&... args) -> OutputIt
|
||||||
|
.. doxygenfunction:: formatted_size(const Locale& loc, format_string<T...> fmt, T&&... args) -> size_t
|
||||||
|
|
||||||
|
.. _legacy-checks:
|
||||||
|
|
||||||
|
Legacy Compile-Time Format String Checks
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
``FMT_STRING`` enables compile-time checks on older compilers. It requires C++14
|
||||||
|
or later and is a no-op in C++11.
|
||||||
|
|
||||||
|
.. doxygendefine:: FMT_STRING
|
||||||
|
|
||||||
|
To force the use of legacy compile-time checks, define the preprocessor variable
|
||||||
|
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
||||||
|
will fail to compile with regular strings.
|
||||||
|
|
||||||
.. _ranges-api:
|
.. _ranges-api:
|
||||||
|
|
||||||
Range and Tuple Formatting
|
Range and Tuple Formatting
|
||||||
|
@ -474,10 +517,11 @@ Standard Library Types Formatting
|
||||||
|
|
||||||
``fmt/std.h`` provides formatters for:
|
``fmt/std.h`` provides formatters for:
|
||||||
|
|
||||||
* `std::filesystem::path <std::filesystem::path>`_
|
* `std::filesystem::path <https://en.cppreference.com/w/cpp/filesystem/path>`_
|
||||||
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
|
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
|
||||||
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
|
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
|
||||||
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
|
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
|
||||||
|
* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_
|
||||||
|
|
||||||
Formatting Variants
|
Formatting Variants
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -505,10 +549,20 @@ Format String Compilation
|
||||||
``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings
|
``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings
|
||||||
marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into
|
marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into
|
||||||
efficient formatting code at compile-time. This supports arguments of built-in
|
efficient formatting code at compile-time. This supports arguments of built-in
|
||||||
and string types as well as user-defined types with ``constexpr`` ``parse``
|
and string types as well as user-defined types with ``format`` functions taking
|
||||||
functions in their ``formatter`` specializations. Format string compilation can
|
the format context type as a template parameter in their ``formatter``
|
||||||
generate more binary code compared to the default API and is only recommended in
|
specializations. For example::
|
||||||
places where formatting is a performance bottleneck.
|
|
||||||
|
template <> struct fmt::formatter<point> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx);
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const point& p, FormatContext& ctx) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
Format string compilation can generate more binary code compared to the default
|
||||||
|
API and is only recommended in places where formatting is a performance
|
||||||
|
bottleneck.
|
||||||
|
|
||||||
.. doxygendefine:: FMT_COMPILE
|
.. doxygendefine:: FMT_COMPILE
|
||||||
|
|
||||||
|
@ -581,7 +635,7 @@ the POSIX extension for positional arguments. Unlike their standard
|
||||||
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
|
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
|
||||||
argument type doesn't match its format specification.
|
argument type doesn't match its format specification.
|
||||||
|
|
||||||
.. doxygenfunction:: printf(const S &format_str, const T&... args)
|
.. doxygenfunction:: printf(string_view fmt, const T&... args) -> int
|
||||||
|
|
||||||
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
|
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
|
||||||
|
|
||||||
|
|
11
doc/build.py
11
doc/build.py
|
@ -4,7 +4,7 @@
|
||||||
import errno, os, re, sys
|
import errno, os, re, sys
|
||||||
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
|
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
|
||||||
|
|
||||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0']
|
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0', '10.1.0', '10.1.1']
|
||||||
|
|
||||||
class Pip:
|
class Pip:
|
||||||
def __init__(self, venv_dir):
|
def __init__(self, venv_dir):
|
||||||
|
@ -49,9 +49,9 @@ def build_docs(version='dev', **kwargs):
|
||||||
GENERATE_MAN = NO
|
GENERATE_MAN = NO
|
||||||
GENERATE_RTF = NO
|
GENERATE_RTF = NO
|
||||||
CASE_SENSE_NAMES = NO
|
CASE_SENSE_NAMES = NO
|
||||||
INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
|
INPUT = {0}/args.h {0}/chrono.h {0}/color.h {0}/core.h \
|
||||||
{0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \
|
{0}/compile.h {0}/format.h {0}/os.h {0}/ostream.h \
|
||||||
{0}/xchar.h
|
{0}/printf.h {0}/xchar.h
|
||||||
QUIET = YES
|
QUIET = YES
|
||||||
JAVADOC_AUTOBRIEF = YES
|
JAVADOC_AUTOBRIEF = YES
|
||||||
AUTOLINK_SUPPORT = NO
|
AUTOLINK_SUPPORT = NO
|
||||||
|
@ -83,8 +83,7 @@ def build_docs(version='dev', **kwargs):
|
||||||
internal_symbols = [
|
internal_symbols = [
|
||||||
'fmt::detail::.*',
|
'fmt::detail::.*',
|
||||||
'basic_data<>',
|
'basic_data<>',
|
||||||
'fmt::type_identity',
|
'fmt::type_identity'
|
||||||
'fmt::dynamic_formatter'
|
|
||||||
]
|
]
|
||||||
noisy_warnings = [
|
noisy_warnings = [
|
||||||
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
|
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
|
||||||
|
|
|
@ -39,7 +39,7 @@ The ``fmt::format`` function returns a string "The answer is 42.". You can use
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
auto out = fmt::memory_buffer();
|
auto out = fmt::memory_buffer();
|
||||||
format_to(std::back_inserter(out),
|
fmt::format_to(std::back_inserter(out),
|
||||||
"For a moment, {} happened.", "nothing");
|
"For a moment, {} happened.", "nothing");
|
||||||
auto data = out.data(); // pointer to the formatted data
|
auto data = out.data(); // pointer to the formatted data
|
||||||
auto size = out.size(); // size of the formatted data
|
auto size = out.size(); // size of the formatted data
|
||||||
|
|
148
doc/syntax.rst
148
doc/syntax.rst
|
@ -109,8 +109,8 @@ Note that unless a minimum field width is defined, the field width will always
|
||||||
be the same size as the data to fill it, so that the alignment option has no
|
be the same size as the data to fill it, so that the alignment option has no
|
||||||
meaning in this case.
|
meaning in this case.
|
||||||
|
|
||||||
The *sign* option is only valid for number types, and can be one of the
|
The *sign* option is only valid for floating point and signed integer types,
|
||||||
following:
|
and can be one of the following:
|
||||||
|
|
||||||
+---------+------------------------------------------------------------+
|
+---------+------------------------------------------------------------+
|
||||||
| Option | Meaning |
|
| Option | Meaning |
|
||||||
|
@ -304,8 +304,8 @@ The available presentation types for pointers are:
|
||||||
Chrono Format Specifications
|
Chrono Format Specifications
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Format specifications for chrono types and ``std::tm`` have the following
|
Format specifications for chrono duration and time point types as well as
|
||||||
syntax:
|
``std::tm`` have the following syntax:
|
||||||
|
|
||||||
.. productionlist:: sf
|
.. productionlist:: sf
|
||||||
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
|
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
|
||||||
|
@ -321,20 +321,89 @@ syntax:
|
||||||
Literal chars are copied unchanged to the output. Precision is valid only for
|
Literal chars are copied unchanged to the output. Precision is valid only for
|
||||||
``std::chrono::duration`` types with a floating-point representation type.
|
``std::chrono::duration`` types with a floating-point representation type.
|
||||||
|
|
||||||
The available presentation types (*chrono_type*) for chrono durations and time
|
The available presentation types (*chrono_type*) are:
|
||||||
points are:
|
|
||||||
|
|
||||||
+---------+--------------------------------------------------------------------+
|
+---------+--------------------------------------------------------------------+
|
||||||
| Type | Meaning |
|
| Type | Meaning |
|
||||||
+=========+====================================================================+
|
+=========+====================================================================+
|
||||||
|
| ``'a'`` | The abbreviated weekday name, e.g. "Sat". If the value does not |
|
||||||
|
| | contain a valid weekday, an exception of type ``format_error`` is |
|
||||||
|
| | thrown. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'A'`` | The full weekday name, e.g. "Saturday". If the value does not |
|
||||||
|
| | contain a valid weekday, an exception of type ``format_error`` is |
|
||||||
|
| | thrown. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'b'`` | The abbreviated month name, e.g. "Nov". If the value does not |
|
||||||
|
| | contain a valid month, an exception of type ``format_error`` is |
|
||||||
|
| | thrown. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'B'`` | The full month name, e.g. "November". If the value does not |
|
||||||
|
| | contain a valid month, an exception of type ``format_error`` is |
|
||||||
|
| | thrown. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'c'`` | The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". |
|
||||||
|
| | The modified command ``%Ec`` produces the locale's alternate date |
|
||||||
|
| | and time representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'C'`` | The year divided by 100 using floored division, e.g. "55". If the |
|
||||||
|
| | result is a single decimal digit, it is prefixed with 0. |
|
||||||
|
| | The modified command ``%EC`` produces the locale's alternative |
|
||||||
|
| | representation of the century. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'d'`` | The day of month as a decimal number. If the result is a single |
|
||||||
|
| | decimal digit, it is prefixed with 0. The modified command ``%Od`` |
|
||||||
|
| | produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'D'`` | Equivalent to ``%m/%d/%y``, e.g. "11/12/55". |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'e'`` | The day of month as a decimal number. If the result is a single |
|
||||||
|
| | decimal digit, it is prefixed with a space. The modified command |
|
||||||
|
| | ``%Oe`` produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'F'`` | Equivalent to ``%Y-%m-%d``, e.g. "1955-11-12". |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'g'`` | The last two decimal digits of the ISO week-based year. If the |
|
||||||
|
| | result is a single digit it is prefixed by 0. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'G'`` | The ISO week-based year as a decimal number. If the result is less |
|
||||||
|
| | than four digits it is left-padded with 0 to four digits. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'h'`` | Equivalent to ``%b``, e.g. "Nov". |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a |
|
| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a |
|
||||||
| | single digit, it is prefixed with 0. The modified command ``%OH`` |
|
| | single digit, it is prefixed with 0. The modified command ``%OH`` |
|
||||||
| | produces the locale's alternative representation. |
|
| | produces the locale's alternative representation. |
|
||||||
+---------+--------------------------------------------------------------------+
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'I'`` | The hour (12-hour clock) as a decimal number. If the result is a |
|
||||||
|
| | single digit, it is prefixed with 0. The modified command ``%OI`` |
|
||||||
|
| | produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'j'`` | If the type being formatted is a specialization of duration, the |
|
||||||
|
| | decimal number of days without padding. Otherwise, the day of the |
|
||||||
|
| | year as a decimal number. Jan 1 is 001. If the result is less than |
|
||||||
|
| | three digits, it is left-padded with 0 to three digits. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'m'`` | The month as a decimal number. Jan is 01. If the result is a |
|
||||||
|
| | single digit, it is prefixed with 0. The modified command ``%Om`` |
|
||||||
|
| | produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
| ``'M'`` | The minute as a decimal number. If the result is a single digit, |
|
| ``'M'`` | The minute as a decimal number. If the result is a single digit, |
|
||||||
| | it is prefixed with 0. The modified command ``%OM`` produces the |
|
| | it is prefixed with 0. The modified command ``%OM`` produces the |
|
||||||
| | locale's alternative representation. |
|
| | locale's alternative representation. |
|
||||||
+---------+--------------------------------------------------------------------+
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'n'`` | A new-line character. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'p'`` | The AM/PM designations associated with a 12-hour clock. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'q'`` | The duration's unit suffix. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'Q'`` | The duration's numeric value (as if extracted via ``.count()``). |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'r'`` | The 12-hour clock time, e.g. "10:04:00 PM". |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'R'`` | Equivalent to ``%H:%M``, e.g. "22:04". |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
|
| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
|
||||||
| | 10, the result is prefixed with 0. If the precision of the input |
|
| | 10, the result is prefixed with 0. If the precision of the input |
|
||||||
| | cannot be exactly represented with seconds, then the format is a |
|
| | cannot be exactly represented with seconds, then the format is a |
|
||||||
|
@ -345,9 +414,66 @@ points are:
|
||||||
| | decimal point is localized according to the locale. The modified |
|
| | decimal point is localized according to the locale. The modified |
|
||||||
| | command ``%OS`` produces the locale's alternative representation. |
|
| | command ``%OS`` produces the locale's alternative representation. |
|
||||||
+---------+--------------------------------------------------------------------+
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'t'`` | A horizontal-tab character. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'T'`` | Equivalent to ``%H:%M:%S``. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'u'`` | The ISO weekday as a decimal number (1-7), where Monday is 1. The |
|
||||||
|
| | modified command ``%Ou`` produces the locale's alternative |
|
||||||
|
| | representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'U'`` | The week number of the year as a decimal number. The first Sunday |
|
||||||
|
| | of the year is the first day of week 01. Days of the same year |
|
||||||
|
| | prior to that are in week 00. If the result is a single digit, it |
|
||||||
|
| | is prefixed with 0. The modified command ``%OU`` produces the |
|
||||||
|
| | locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'V'`` | The ISO week-based week number as a decimal number. If the result |
|
||||||
|
| | is a single digit, it is prefixed with 0. The modified command |
|
||||||
|
| | ``%OV`` produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'w'`` | The weekday as a decimal number (0-6), where Sunday is 0. |
|
||||||
|
| | The modified command ``%Ow`` produces the locale's alternative |
|
||||||
|
| | representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'W'`` | The week number of the year as a decimal number. The first Monday |
|
||||||
|
| | of the year is the first day of week 01. Days of the same year |
|
||||||
|
| | prior to that are in week 00. If the result is a single digit, it |
|
||||||
|
| | is prefixed with 0. The modified command ``%OW`` produces the |
|
||||||
|
| | locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'x'`` | The date representation, e.g. "11/12/55". The modified command |
|
||||||
|
| | ``%Ex`` produces the locale's alternate date representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'X'`` | The time representation, e.g. "10:04:00". The modified command |
|
||||||
|
| | ``%EX`` produces the locale's alternate time representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'y'`` | The last two decimal digits of the year. If the result is a single |
|
||||||
|
| | digit it is prefixed by 0. The modified command ``%Oy`` produces |
|
||||||
|
| | the locale's alternative representation. The modified command |
|
||||||
|
| | ``%Ey`` produces the locale's alternative representation of offset |
|
||||||
|
| | from ``%EC`` (year only). |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'Y'`` | The year as a decimal number. If the result is less than four |
|
||||||
|
| | digits it is left-padded with 0 to four digits. The modified |
|
||||||
|
| | command ``%EY`` produces the locale's alternative full year |
|
||||||
|
| | representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'z'`` | The offset from UTC in the ISO 8601:2004 format. For example -0430 |
|
||||||
|
| | refers to 4 hours 30 minutes behind UTC. If the offset is zero, |
|
||||||
|
| | +0000 is used. The modified commands ``%Ez`` and ``%Oz`` insert a |
|
||||||
|
| | ``:`` between the hours and minutes: -04:30. If the offset |
|
||||||
|
| | information is not available, an exception of type |
|
||||||
|
| | ``format_error`` is thrown. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'Z'`` | The time zone abbreviation. If the time zone abbreviation is not |
|
||||||
|
| | available, an exception of type ``format_error`` is thrown. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'%'`` | A % character. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
|
||||||
Specifiers that have a calendaric component such as ``'d'`` (the day of month)
|
Specifiers that have a calendaric component such as ``'d'`` (the day of month)
|
||||||
are valid only for ``std::tm`` and not durations or time points.
|
are valid only for ``std::tm`` and time points but not durations.
|
||||||
|
|
||||||
.. range-specs:
|
.. range-specs:
|
||||||
|
|
||||||
|
@ -356,8 +482,8 @@ Range Format Specifications
|
||||||
|
|
||||||
Format specifications for range types have the following syntax:
|
Format specifications for range types have the following syntax:
|
||||||
|
|
||||||
..productionlist:: sf
|
.. productionlist:: sf
|
||||||
range_format_spec: [":" [`underlying_spec`]]
|
range_format_spec: [":" [`underlying_spec`]]
|
||||||
|
|
||||||
The `underlying_spec` is parsed based on the formatter of the range's
|
The `underlying_spec` is parsed based on the formatter of the range's
|
||||||
reference type.
|
reference type.
|
||||||
|
@ -366,12 +492,12 @@ By default, a range of characters or strings is printed escaped and quoted. But
|
||||||
if any `underlying_spec` is provided (even if it is empty), then the characters
|
if any `underlying_spec` is provided (even if it is empty), then the characters
|
||||||
or strings are printed according to the provided specification.
|
or strings are printed according to the provided specification.
|
||||||
|
|
||||||
Examples:
|
Examples::
|
||||||
|
|
||||||
fmt::format("{}", std::vector{10, 20, 30});
|
fmt::format("{}", std::vector{10, 20, 30});
|
||||||
// Result: [10, 20, 30]
|
// Result: [10, 20, 30]
|
||||||
fmt::format("{::#x}", std::vector{10, 20, 30});
|
fmt::format("{::#x}", std::vector{10, 20, 30});
|
||||||
// Result: [0xa, 0x14, 0x13]
|
// Result: [0xa, 0x14, 0x1e]
|
||||||
fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'});
|
fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'});
|
||||||
// Result: ['h', 'e', 'l', 'l', 'o']
|
// Result: ['h', 'e', 'l', 'l', 'o']
|
||||||
fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'});
|
fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Formatting library for C++ - dynamic format arguments
|
// Formatting library for C++ - dynamic argument lists
|
||||||
//
|
//
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
FMT_MODULE_EXPORT_BEGIN
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
enum class color : uint32_t {
|
enum class color : uint32_t {
|
||||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||||
|
@ -203,7 +203,7 @@ struct rgb {
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_BEGIN_DETAIL_NAMESPACE
|
namespace detail {
|
||||||
|
|
||||||
// color is a struct of either a rgb color or a terminal color.
|
// color is a struct of either a rgb color or a terminal color.
|
||||||
struct color_type {
|
struct color_type {
|
||||||
|
@ -225,8 +225,7 @@ struct color_type {
|
||||||
uint32_t rgb_color;
|
uint32_t rgb_color;
|
||||||
} value;
|
} value;
|
||||||
};
|
};
|
||||||
|
} // namespace detail
|
||||||
FMT_END_DETAIL_NAMESPACE
|
|
||||||
|
|
||||||
/** A text style consisting of foreground and background colors and emphasis. */
|
/** A text style consisting of foreground and background colors and emphasis. */
|
||||||
class text_style {
|
class text_style {
|
||||||
|
@ -323,7 +322,7 @@ FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
|
||||||
return text_style(lhs) | rhs;
|
return text_style(lhs) | rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_BEGIN_DETAIL_NAMESPACE
|
namespace detail {
|
||||||
|
|
||||||
template <typename Char> struct ansi_color_escape {
|
template <typename Char> struct ansi_color_escape {
|
||||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||||
|
@ -423,26 +422,6 @@ FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
|
||||||
return ansi_color_escape<Char>(em);
|
return ansi_color_escape<Char>(em);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char> inline void fputs(const Char* chars, FILE* stream) {
|
|
||||||
int result = std::fputs(chars, stream);
|
|
||||||
if (result < 0)
|
|
||||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <> inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) {
|
|
||||||
int result = std::fputws(chars, stream);
|
|
||||||
if (result < 0)
|
|
||||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char> inline void reset_color(FILE* stream) {
|
|
||||||
fputs("\x1b[0m", stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <> inline void reset_color<wchar_t>(FILE* stream) {
|
|
||||||
fputs(L"\x1b[0m", stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
||||||
auto reset_color = string_view("\x1b[0m");
|
auto reset_color = string_view("\x1b[0m");
|
||||||
buffer.append(reset_color.begin(), reset_color.end());
|
buffer.append(reset_color.begin(), reset_color.end());
|
||||||
|
@ -477,19 +456,21 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||||
if (has_style) detail::reset_color<Char>(buf);
|
if (has_style) detail::reset_color<Char>(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_END_DETAIL_NAMESPACE
|
} // namespace detail
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
|
||||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
format_args args) {
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
// Legacy wide streams are not supported.
|
||||||
basic_memory_buffer<Char> buf;
|
auto buf = memory_buffer();
|
||||||
detail::vformat_to(buf, ts, detail::to_string_view(format), args);
|
detail::vformat_to(buf, ts, fmt, args);
|
||||||
if (detail::is_utf8()) {
|
if (detail::is_utf8()) {
|
||||||
detail::print(f, basic_string_view<Char>(buf.begin(), buf.size()));
|
detail::print(f, string_view(buf.begin(), buf.size()));
|
||||||
} else {
|
return;
|
||||||
buf.push_back(Char(0));
|
|
||||||
detail::fputs(buf.data(), f);
|
|
||||||
}
|
}
|
||||||
|
buf.push_back('\0');
|
||||||
|
int result = std::fputs(buf.data(), f);
|
||||||
|
if (result < 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -566,7 +547,7 @@ OutputIt vformat_to(
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
auto&& buf = detail::get_buffer<Char>(out);
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
detail::vformat_to(buf, ts, format_str, args);
|
detail::vformat_to(buf, ts, format_str, args);
|
||||||
return detail::get_iterator(buf);
|
return detail::get_iterator(buf, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -634,7 +615,7 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
|
||||||
|
|
||||||
**Example**::
|
**Example**::
|
||||||
|
|
||||||
fmt::print("Elapsed time: {s:.2f} seconds",
|
fmt::print("Elapsed time: {0:.2f} seconds",
|
||||||
fmt::styled(1.23, fmt::fg(fmt::color::green) |
|
fmt::styled(1.23, fmt::fg(fmt::color::green) |
|
||||||
fmt::bg(fmt::color::blue)));
|
fmt::bg(fmt::color::blue)));
|
||||||
\endrst
|
\endrst
|
||||||
|
@ -645,7 +626,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts)
|
||||||
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
|
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_END
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_COLOR_H_
|
#endif // FMT_COLOR_H_
|
||||||
|
|
|
@ -14,89 +14,11 @@ FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename Char, typename InputIt>
|
template <typename Char, typename InputIt>
|
||||||
inline counting_iterator copy_str(InputIt begin, InputIt end,
|
FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||||
counting_iterator it) {
|
counting_iterator it) {
|
||||||
return it + (end - begin);
|
return it + (end - begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt> class truncating_iterator_base {
|
|
||||||
protected:
|
|
||||||
OutputIt out_;
|
|
||||||
size_t limit_;
|
|
||||||
size_t count_ = 0;
|
|
||||||
|
|
||||||
truncating_iterator_base() : out_(), limit_(0) {}
|
|
||||||
|
|
||||||
truncating_iterator_base(OutputIt out, size_t limit)
|
|
||||||
: out_(out), limit_(limit) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
using iterator_category = std::output_iterator_tag;
|
|
||||||
using value_type = typename std::iterator_traits<OutputIt>::value_type;
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using pointer = void;
|
|
||||||
using reference = void;
|
|
||||||
FMT_UNCHECKED_ITERATOR(truncating_iterator_base);
|
|
||||||
|
|
||||||
OutputIt base() const { return out_; }
|
|
||||||
size_t count() const { return count_; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// An output iterator that truncates the output and counts the number of objects
|
|
||||||
// written to it.
|
|
||||||
template <typename OutputIt,
|
|
||||||
typename Enable = typename std::is_void<
|
|
||||||
typename std::iterator_traits<OutputIt>::value_type>::type>
|
|
||||||
class truncating_iterator;
|
|
||||||
|
|
||||||
template <typename OutputIt>
|
|
||||||
class truncating_iterator<OutputIt, std::false_type>
|
|
||||||
: public truncating_iterator_base<OutputIt> {
|
|
||||||
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
|
|
||||||
|
|
||||||
truncating_iterator() = default;
|
|
||||||
|
|
||||||
truncating_iterator(OutputIt out, size_t limit)
|
|
||||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
|
||||||
|
|
||||||
truncating_iterator& operator++() {
|
|
||||||
if (this->count_++ < this->limit_) ++this->out_;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
truncating_iterator operator++(int) {
|
|
||||||
auto it = *this;
|
|
||||||
++*this;
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type& operator*() const {
|
|
||||||
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename OutputIt>
|
|
||||||
class truncating_iterator<OutputIt, std::true_type>
|
|
||||||
: public truncating_iterator_base<OutputIt> {
|
|
||||||
public:
|
|
||||||
truncating_iterator() = default;
|
|
||||||
|
|
||||||
truncating_iterator(OutputIt out, size_t limit)
|
|
||||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
|
||||||
|
|
||||||
template <typename T> truncating_iterator& operator=(T val) {
|
|
||||||
if (this->count_++ < this->limit_) *this->out_++ = val;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
truncating_iterator& operator++() { return *this; }
|
|
||||||
truncating_iterator& operator++(int) { return *this; }
|
|
||||||
truncating_iterator& operator*() { return *this; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// A compile-time string which is compiled into fast formatting code.
|
// A compile-time string which is compiled into fast formatting code.
|
||||||
class compiled_string {};
|
class compiled_string {};
|
||||||
|
|
||||||
|
@ -196,7 +118,8 @@ template <typename Char> struct code_unit {
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||||
return write<Char>(out, value);
|
*out++ = value;
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -220,7 +143,12 @@ template <typename Char, typename T, int N> struct field {
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
return write<Char>(out, get_arg_checked<T, N>(args...));
|
const T& arg = get_arg_checked<T, N>(args...);
|
||||||
|
if constexpr (std::is_convertible_v<T, basic_string_view<Char>>) {
|
||||||
|
auto s = basic_string_view<Char>(arg);
|
||||||
|
return copy_str<Char>(s.begin(), s.end(), out);
|
||||||
|
}
|
||||||
|
return write<Char>(out, arg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -331,39 +259,35 @@ template <typename T, typename Char> struct parse_specs_result {
|
||||||
int next_arg_id;
|
int next_arg_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr int manual_indexing_id = -1;
|
enum { manual_indexing_id = -1 };
|
||||||
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||||
size_t pos, int next_arg_id) {
|
size_t pos, int next_arg_id) {
|
||||||
str.remove_prefix(pos);
|
str.remove_prefix(pos);
|
||||||
auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {},
|
auto ctx =
|
||||||
next_arg_id);
|
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
|
||||||
auto f = formatter<T, Char>();
|
auto f = formatter<T, Char>();
|
||||||
auto end = f.parse(ctx);
|
auto end = f.parse(ctx);
|
||||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
|
||||||
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char> struct arg_id_handler {
|
template <typename Char> struct arg_id_handler {
|
||||||
arg_ref<Char> arg_id;
|
arg_ref<Char> arg_id;
|
||||||
|
|
||||||
constexpr int operator()() {
|
constexpr int on_auto() {
|
||||||
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
constexpr int operator()(int id) {
|
constexpr int on_index(int id) {
|
||||||
arg_id = arg_ref<Char>(id);
|
arg_id = arg_ref<Char>(id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
constexpr int operator()(basic_string_view<Char> id) {
|
constexpr int on_name(basic_string_view<Char> id) {
|
||||||
arg_id = arg_ref<Char>(id);
|
arg_id = arg_ref<Char>(id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void on_error(const char* message) {
|
|
||||||
FMT_THROW(format_error(message));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char> struct parse_arg_id_result {
|
template <typename Char> struct parse_arg_id_result {
|
||||||
|
@ -397,13 +321,20 @@ constexpr auto parse_replacement_field_then_tail(S format_str) {
|
||||||
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||||
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
||||||
format_str);
|
format_str);
|
||||||
} else if constexpr (c == ':') {
|
} else if constexpr (c != ':') {
|
||||||
|
FMT_THROW(format_error("expected ':'"));
|
||||||
|
} else {
|
||||||
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||||
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||||
return parse_tail<Args, result.end, result.next_arg_id>(
|
if constexpr (result.end >= str.size() || str[result.end] != '}') {
|
||||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
FMT_THROW(format_error("expected '}'"));
|
||||||
result.fmt},
|
return 0;
|
||||||
format_str);
|
} else {
|
||||||
|
return parse_tail<Args, result.end + 1, result.next_arg_id>(
|
||||||
|
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||||
|
result.fmt},
|
||||||
|
format_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,20 +376,18 @@ constexpr auto compile_format_string(S format_str) {
|
||||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||||
constexpr auto arg_index =
|
constexpr auto arg_index =
|
||||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||||
if constexpr (arg_index != invalid_arg_index) {
|
if constexpr (arg_index >= 0) {
|
||||||
constexpr auto next_id =
|
constexpr auto next_id =
|
||||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||||
return parse_replacement_field_then_tail<
|
return parse_replacement_field_then_tail<
|
||||||
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||||
arg_index, next_id>(format_str);
|
arg_index, next_id>(format_str);
|
||||||
} else {
|
} else if constexpr (c == '}') {
|
||||||
if constexpr (c == '}') {
|
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
format_str);
|
||||||
format_str);
|
} else if constexpr (c == ':') {
|
||||||
} else if constexpr (c == ':') {
|
return unknown_format(); // no type info for specs parsing
|
||||||
return unknown_format(); // no type info for specs parsing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -494,7 +423,7 @@ constexpr auto compile(S format_str) {
|
||||||
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_BEGIN
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||||
|
|
||||||
|
@ -561,14 +490,16 @@ template <typename OutputIt, typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||||
const S& format_str, Args&&... args) {
|
const S& format_str, Args&&... args) {
|
||||||
auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n),
|
using traits = detail::fixed_buffer_traits;
|
||||||
format_str, std::forward<Args>(args)...);
|
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||||
return {it.base(), it.count()};
|
format_to(std::back_inserter(buf), format_str, std::forward<Args>(args)...);
|
||||||
|
return {buf.out(), buf.count()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
size_t formatted_size(const S& format_str, const Args&... args) {
|
FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
|
||||||
|
const Args&... args) {
|
||||||
return fmt::format_to(detail::counting_iterator(), format_str, args...)
|
return fmt::format_to(detail::counting_iterator(), format_str, args...)
|
||||||
.count();
|
.count();
|
||||||
}
|
}
|
||||||
|
@ -597,7 +528,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
|
||||||
} // namespace literals
|
} // namespace literals
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_END
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_COMPILE_H_
|
#endif // FMT_COMPILE_H_
|
||||||
|
|
1830
include/fmt/core.h
1830
include/fmt/core.h
File diff suppressed because it is too large
Load diff
|
@ -9,13 +9,9 @@
|
||||||
#define FMT_FORMAT_INL_H_
|
#define FMT_FORMAT_INL_H_
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
|
||||||
#include <cerrno> // errno
|
#include <cerrno> // errno
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstring> // std::memmove
|
|
||||||
#include <cwchar>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
|
||||||
return '.';
|
return '.';
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
FMT_FUNC auto write_loc(appender out, loc_value value,
|
||||||
|
const format_specs<>& specs, locale_ref loc) -> bool {
|
||||||
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
auto locale = loc.get<std::locale>();
|
||||||
|
// We cannot use the num_put<char> facet because it may produce output in
|
||||||
|
// a wrong encoding.
|
||||||
|
using facet = format_facet<std::locale>;
|
||||||
|
if (std::has_facet<facet>(locale))
|
||||||
|
return std::use_facet<facet>(locale).put(out, value, specs);
|
||||||
|
return facet(locale).put(out, value, specs);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
#if !FMT_MSC_VERSION
|
template <typename Locale> typename Locale::id format_facet<Locale>::id;
|
||||||
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
|
|
||||||
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
|
||||||
|
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
|
||||||
|
grouping_ = numpunct.grouping();
|
||||||
|
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
|
||||||
|
appender out, loc_value val, const format_specs<>& specs) const -> bool {
|
||||||
|
return val.visit(
|
||||||
|
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_});
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str,
|
FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
|
||||||
format_args args) {
|
format_args args) {
|
||||||
auto ec = std::error_code(error_code, std::generic_category());
|
auto ec = std::error_code(error_code, std::generic_category());
|
||||||
return std::system_error(ec, vformat(format_str, args));
|
return std::system_error(ec, vformat(fmt, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
|
||||||
return (n >> r) | (n << (64 - r));
|
return (n >> r) | (n << (64 - r));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
|
|
||||||
inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept {
|
|
||||||
#if FMT_USE_INT128
|
|
||||||
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
|
|
||||||
return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};
|
|
||||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
|
||||||
auto result = uint128_fallback();
|
|
||||||
result.lo_ = _umul128(x, y, &result.hi_);
|
|
||||||
return result;
|
|
||||||
#else
|
|
||||||
const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>());
|
|
||||||
|
|
||||||
uint64_t a = x >> 32;
|
|
||||||
uint64_t b = x & mask;
|
|
||||||
uint64_t c = y >> 32;
|
|
||||||
uint64_t d = y & mask;
|
|
||||||
|
|
||||||
uint64_t ac = a * c;
|
|
||||||
uint64_t bc = b * c;
|
|
||||||
uint64_t ad = a * d;
|
|
||||||
uint64_t bd = b * d;
|
|
||||||
|
|
||||||
uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
|
|
||||||
|
|
||||||
return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
|
|
||||||
(intermediate << 32) + (bd & mask)};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
|
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
|
||||||
namespace dragonbox {
|
namespace dragonbox {
|
||||||
// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
|
|
||||||
inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept {
|
|
||||||
#if FMT_USE_INT128
|
|
||||||
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
|
|
||||||
return static_cast<uint64_t>(p >> 64);
|
|
||||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
|
||||||
return __umulh(x, y);
|
|
||||||
#else
|
|
||||||
return umul128(x, y).high();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a
|
|
||||||
// 128-bit unsigned integer.
|
|
||||||
inline uint128_fallback umul192_upper128(uint64_t x,
|
|
||||||
uint128_fallback y) noexcept {
|
|
||||||
uint128_fallback r = umul128(x, y.high());
|
|
||||||
r += umul128_upper64(x, y.low());
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
|
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
|
||||||
// 64-bit unsigned integer.
|
// 64-bit unsigned integer.
|
||||||
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
|
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
|
||||||
|
@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
|
||||||
return x * y;
|
return x * y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from
|
|
||||||
// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.
|
|
||||||
inline int floor_log10_pow2(int e) noexcept {
|
|
||||||
FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent");
|
|
||||||
static_assert((-1 >> 1) == -1, "right shift is not arithmetic");
|
|
||||||
return (e * 315653) >> 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Various fast log computations.
|
// Various fast log computations.
|
||||||
inline int floor_log2_pow10(int e) noexcept {
|
|
||||||
FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
|
|
||||||
return (e * 1741647) >> 19;
|
|
||||||
}
|
|
||||||
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
|
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
|
||||||
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
|
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
|
||||||
return (e * 631305 - 261663) >> 21;
|
return (e * 631305 - 261663) >> 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr struct {
|
FMT_INLINE_VARIABLE constexpr struct {
|
||||||
uint32_t divisor;
|
uint32_t divisor;
|
||||||
int shift_amount;
|
int shift_amount;
|
||||||
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
|
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
|
||||||
|
@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Various subroutines using pow10 cache
|
// Various subroutines using pow10 cache
|
||||||
template <class T> struct cache_accessor;
|
template <typename T> struct cache_accessor;
|
||||||
|
|
||||||
template <> struct cache_accessor<float> {
|
template <> struct cache_accessor<float> {
|
||||||
using carrier_uint = float_info<float>::carrier_uint;
|
using carrier_uint = float_info<float>::carrier_uint;
|
||||||
|
@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> {
|
||||||
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
|
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
|
||||||
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
|
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
|
||||||
{0xc5a05277621be293, 0xc7098b7305241886},
|
{0xc5a05277621be293, 0xc7098b7305241886},
|
||||||
{ 0xf70867153aa2db38,
|
{0xf70867153aa2db38, 0xb8cbee4fc66d1ea8},
|
||||||
0xb8cbee4fc66d1ea8 }
|
{0x9a65406d44a5c903, 0x737f74f1dc043329},
|
||||||
|
{0xc0fe908895cf3b44, 0x505f522e53053ff3},
|
||||||
|
{0xf13e34aabb430a15, 0x647726b9e7c68ff0},
|
||||||
|
{0x96c6e0eab509e64d, 0x5eca783430dc19f6},
|
||||||
|
{0xbc789925624c5fe0, 0xb67d16413d132073},
|
||||||
|
{0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890},
|
||||||
|
{0x933e37a534cbaae7, 0x8e91b962f7b6f15a},
|
||||||
|
{0xb80dc58e81fe95a1, 0x723627bbb5a4adb1},
|
||||||
|
{0xe61136f2227e3b09, 0xcec3b1aaa30dd91d},
|
||||||
|
{0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2},
|
||||||
|
{0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e},
|
||||||
|
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
|
||||||
|
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
|
||||||
|
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
|
||||||
|
{ 0xdb68c2ca82ed2a05,
|
||||||
|
0xa67398db9f6820e2 }
|
||||||
#else
|
#else
|
||||||
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
|
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
|
||||||
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
|
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
|
||||||
|
@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> {
|
||||||
{0x8da471a9de737e24, 0x5ceaecfed289e5d3},
|
{0x8da471a9de737e24, 0x5ceaecfed289e5d3},
|
||||||
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
|
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
|
||||||
{0xb8da1662e7b00a17, 0x3d6a751f3b936244},
|
{0xb8da1662e7b00a17, 0x3d6a751f3b936244},
|
||||||
{ 0x95527a5202df0ccb,
|
{0x95527a5202df0ccb, 0x0f37801e0c43ebc9},
|
||||||
0x0f37801e0c43ebc9 }
|
{0xf13e34aabb430a15, 0x647726b9e7c68ff0}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1138,8 +1114,12 @@ template <> struct cache_accessor<double> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
|
||||||
|
return cache_accessor<double>::get_cached_power(k);
|
||||||
|
}
|
||||||
|
|
||||||
// Various integer checks
|
// Various integer checks
|
||||||
template <class T>
|
template <typename T>
|
||||||
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
|
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
|
||||||
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
|
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
|
||||||
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
|
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
|
||||||
|
@ -1148,12 +1128,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove trailing zeros from n and return the number of zeros removed (float)
|
// Remove trailing zeros from n and return the number of zeros removed (float)
|
||||||
FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
|
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
|
||||||
FMT_ASSERT(n != 0, "");
|
FMT_ASSERT(n != 0, "");
|
||||||
const uint32_t mod_inv_5 = 0xcccccccd;
|
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
|
||||||
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
constexpr uint32_t mod_inv_5 = 0xcccccccd;
|
||||||
|
constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5
|
||||||
|
|
||||||
int s = 0;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
auto q = rotr(n * mod_inv_25, 2);
|
auto q = rotr(n * mod_inv_25, 2);
|
||||||
if (q > max_value<uint32_t>() / 100) break;
|
if (q > max_value<uint32_t>() / 100) break;
|
||||||
|
@ -1165,7 +1145,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
|
||||||
n = q;
|
n = q;
|
||||||
s |= 1;
|
s |= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1179,32 +1158,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
|
||||||
|
|
||||||
// Is n is divisible by 10^8?
|
// Is n is divisible by 10^8?
|
||||||
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
|
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
|
||||||
// If yes, work with the quotient.
|
// If yes, work with the quotient...
|
||||||
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
|
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
|
||||||
|
// ... and use the 32 bit variant of the function
|
||||||
const uint32_t mod_inv_5 = 0xcccccccd;
|
int s = remove_trailing_zeros(n32, 8);
|
||||||
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
|
||||||
|
|
||||||
int s = 8;
|
|
||||||
while (true) {
|
|
||||||
auto q = rotr(n32 * mod_inv_25, 2);
|
|
||||||
if (q > max_value<uint32_t>() / 100) break;
|
|
||||||
n32 = q;
|
|
||||||
s += 2;
|
|
||||||
}
|
|
||||||
auto q = rotr(n32 * mod_inv_5, 1);
|
|
||||||
if (q <= max_value<uint32_t>() / 10) {
|
|
||||||
n32 = q;
|
|
||||||
s |= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
n = n32;
|
n = n32;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If n is not divisible by 10^8, work with n itself.
|
// If n is not divisible by 10^8, work with n itself.
|
||||||
const uint64_t mod_inv_5 = 0xcccccccccccccccd;
|
constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;
|
||||||
const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // = mod_inv_5 * mod_inv_5
|
||||||
|
|
||||||
int s = 0;
|
int s = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -1223,7 +1187,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The main algorithm for shorter interval case
|
// The main algorithm for shorter interval case
|
||||||
template <class T>
|
template <typename T>
|
||||||
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
|
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
|
||||||
decimal_fp<T> ret_value;
|
decimal_fp<T> ret_value;
|
||||||
// Compute k and beta
|
// Compute k and beta
|
||||||
|
@ -1337,7 +1301,7 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
|
||||||
|
|
||||||
if (r < deltai) {
|
if (r < deltai) {
|
||||||
// Exclude the right endpoint if necessary.
|
// Exclude the right endpoint if necessary.
|
||||||
if (r == 0 && z_mul.is_integer && !include_right_endpoint) {
|
if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) {
|
||||||
--ret_value.significand;
|
--ret_value.significand;
|
||||||
r = float_info<T>::big_divisor;
|
r = float_info<T>::big_divisor;
|
||||||
goto small_divisor_case_label;
|
goto small_divisor_case_label;
|
||||||
|
@ -1346,26 +1310,11 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
|
||||||
goto small_divisor_case_label;
|
goto small_divisor_case_label;
|
||||||
} else {
|
} else {
|
||||||
// r == deltai; compare fractional parts.
|
// r == deltai; compare fractional parts.
|
||||||
const carrier_uint two_fl = two_fc - 1;
|
const typename cache_accessor<T>::compute_mul_parity_result x_mul =
|
||||||
|
cache_accessor<T>::compute_mul_parity(two_fc - 1, cache, beta);
|
||||||
|
|
||||||
if (!include_left_endpoint ||
|
if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint)))
|
||||||
exponent < float_info<T>::case_fc_pm_half_lower_threshold ||
|
goto small_divisor_case_label;
|
||||||
exponent > float_info<T>::divisibility_check_by_5_threshold) {
|
|
||||||
// If the left endpoint is not included, the condition for
|
|
||||||
// success is z^(f) < delta^(f) (odd parity).
|
|
||||||
// Otherwise, the inequalities on exponent ensure that
|
|
||||||
// x is not an integer, so if z^(f) >= delta^(f) (even parity), we in fact
|
|
||||||
// have strict inequality.
|
|
||||||
if (!cache_accessor<T>::compute_mul_parity(two_fl, cache, beta).parity) {
|
|
||||||
goto small_divisor_case_label;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const typename cache_accessor<T>::compute_mul_parity_result x_mul =
|
|
||||||
cache_accessor<T>::compute_mul_parity(two_fl, cache, beta);
|
|
||||||
if (!x_mul.parity && !x_mul.is_integer) {
|
|
||||||
goto small_divisor_case_label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ret_value.exponent = minus_k + float_info<T>::kappa + 1;
|
ret_value.exponent = minus_k + float_info<T>::kappa + 1;
|
||||||
|
|
||||||
|
@ -1404,22 +1353,11 @@ small_divisor_case_label:
|
||||||
// or equivalently, when y is an integer.
|
// or equivalently, when y is an integer.
|
||||||
if (y_mul.parity != approx_y_parity)
|
if (y_mul.parity != approx_y_parity)
|
||||||
--ret_value.significand;
|
--ret_value.significand;
|
||||||
else if (y_mul.is_integer && ret_value.significand % 2 != 0)
|
else if (y_mul.is_integer & (ret_value.significand % 2 != 0))
|
||||||
--ret_value.significand;
|
--ret_value.significand;
|
||||||
return ret_value;
|
return ret_value;
|
||||||
}
|
}
|
||||||
} // namespace dragonbox
|
} // namespace dragonbox
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...)
|
|
||||||
-> int {
|
|
||||||
auto args = va_list();
|
|
||||||
va_start(args, fmt);
|
|
||||||
int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <> struct formatter<detail::bigint> {
|
template <> struct formatter<detail::bigint> {
|
||||||
|
@ -1428,9 +1366,8 @@ template <> struct formatter<detail::bigint> {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
auto format(const detail::bigint& n, format_context& ctx) const
|
||||||
auto format(const detail::bigint& n, FormatContext& ctx) const ->
|
-> format_context::iterator {
|
||||||
typename FormatContext::iterator {
|
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (auto i = n.bigits_.size(); i > 0; --i) {
|
for (auto i = n.bigits_.size(); i > 0; --i) {
|
||||||
|
@ -1488,53 +1425,45 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
|
||||||
return to_string(buffer);
|
return to_string(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
#ifndef _WIN32
|
||||||
|
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
|
||||||
|
#else
|
||||||
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
|
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
|
||||||
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
|
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
|
||||||
void*, const void*, dword, dword*, void*);
|
void*, const void*, dword, dword*, void*);
|
||||||
} // namespace detail
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace detail {
|
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
|
||||||
FMT_FUNC void print(std::FILE* f, string_view text) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
auto fd = _fileno(f);
|
auto fd = _fileno(f);
|
||||||
if (_isatty(fd)) {
|
if (!_isatty(fd)) return false;
|
||||||
detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
|
auto u16 = utf8_to_utf16(text);
|
||||||
auto written = detail::dword();
|
auto written = dword();
|
||||||
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
|
return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
|
||||||
u16.c_str(), static_cast<uint32_t>(u16.size()),
|
static_cast<uint32_t>(u16.size()), &written, nullptr) != 0;
|
||||||
&written, nullptr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fallback to fwrite on failure. It can happen if the output has been
|
|
||||||
// redirected to NUL.
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
detail::fwrite_fully(text.data(), 1, text.size(), f);
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
|
|
||||||
memory_buffer buffer;
|
|
||||||
detail::vformat_to(buffer, format_str, args);
|
|
||||||
detail::print(f, {buffer.data(), buffer.size()});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Print assuming legacy (non-Unicode) encoding.
|
// Print assuming legacy (non-Unicode) encoding.
|
||||||
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
|
FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
|
||||||
format_args args) {
|
auto buffer = memory_buffer();
|
||||||
memory_buffer buffer;
|
detail::vformat_to(buffer, fmt,
|
||||||
detail::vformat_to(buffer, format_str,
|
|
||||||
basic_format_args<buffer_context<char>>(args));
|
basic_format_args<buffer_context<char>>(args));
|
||||||
fwrite_fully(buffer.data(), 1, buffer.size(), f);
|
fwrite_fully(buffer.data(), 1, buffer.size(), f);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FMT_FUNC void vprint(string_view format_str, format_args args) {
|
FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||||
vprint(stdout, format_str, args);
|
if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
|
||||||
|
auto buffer = memory_buffer();
|
||||||
|
detail::vformat_to(buffer, fmt, args);
|
||||||
|
detail::print(f, {buffer.data(), buffer.size()});
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_FUNC void vprint(string_view fmt, format_args args) {
|
||||||
|
vprint(stdout, fmt, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
1956
include/fmt/format.h
1956
include/fmt/format.h
File diff suppressed because it is too large
Load diff
115
include/fmt/os.h
115
include/fmt/os.h
|
@ -71,7 +71,7 @@
|
||||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
FMT_MODULE_EXPORT_BEGIN
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\rst
|
\rst
|
||||||
|
@ -120,51 +120,13 @@ template <typename Char> class basic_cstring_view {
|
||||||
using cstring_view = basic_cstring_view<char>;
|
using cstring_view = basic_cstring_view<char>;
|
||||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||||
|
|
||||||
template <typename Char> struct formatter<std::error_code, Char> {
|
|
||||||
template <typename ParseContext>
|
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
|
||||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
|
||||||
-> decltype(ctx.out()) {
|
|
||||||
auto out = ctx.out();
|
|
||||||
out = detail::write_bytes(out, ec.category().name(),
|
|
||||||
basic_format_specs<Char>());
|
|
||||||
out = detail::write<Char>(out, Char(':'));
|
|
||||||
out = detail::write<Char>(out, ec.value());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
FMT_API const std::error_category& system_category() noexcept;
|
FMT_API const std::error_category& system_category() noexcept;
|
||||||
|
|
||||||
FMT_BEGIN_DETAIL_NAMESPACE
|
namespace detail {
|
||||||
// A converter from UTF-16 to UTF-8.
|
|
||||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
|
||||||
class utf16_to_utf8 {
|
|
||||||
private:
|
|
||||||
memory_buffer buffer_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
utf16_to_utf8() {}
|
|
||||||
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
|
||||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
|
||||||
size_t size() const { return buffer_.size() - 1; }
|
|
||||||
const char* c_str() const { return &buffer_[0]; }
|
|
||||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
|
||||||
|
|
||||||
// Performs conversion returning a system error code instead of
|
|
||||||
// throwing exception on conversion error. This method may still throw
|
|
||||||
// in case of memory allocation error.
|
|
||||||
FMT_API int convert(basic_string_view<wchar_t> s);
|
|
||||||
};
|
|
||||||
|
|
||||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||||
const char* message) noexcept;
|
const char* message) noexcept;
|
||||||
FMT_END_DETAIL_NAMESPACE
|
}
|
||||||
|
|
||||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||||
format_args args);
|
format_args args);
|
||||||
|
@ -355,12 +317,18 @@ class FMT_API file {
|
||||||
// Creates a buffered_file object associated with this file and detaches
|
// Creates a buffered_file object associated with this file and detaches
|
||||||
// this file object from the file.
|
// this file object from the file.
|
||||||
buffered_file fdopen(const char* mode);
|
buffered_file fdopen(const char* mode);
|
||||||
|
|
||||||
|
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||||
|
// Opens a file and constructs a file object representing this file by
|
||||||
|
// wcstring_view filename. Windows only.
|
||||||
|
static file open_windows_file(wcstring_view path, int oflag);
|
||||||
|
# endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the memory page size.
|
// Returns the memory page size.
|
||||||
long getpagesize();
|
long getpagesize();
|
||||||
|
|
||||||
FMT_BEGIN_DETAIL_NAMESPACE
|
namespace detail {
|
||||||
|
|
||||||
struct buffer_size {
|
struct buffer_size {
|
||||||
buffer_size() = default;
|
buffer_size() = default;
|
||||||
|
@ -397,56 +365,61 @@ struct ostream_params {
|
||||||
# endif
|
# endif
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_END_DETAIL_NAMESPACE
|
class file_buffer final : public buffer<char> {
|
||||||
|
file file_;
|
||||||
|
|
||||||
|
FMT_API void grow(size_t) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMT_API file_buffer(cstring_view path, const ostream_params& params);
|
||||||
|
FMT_API file_buffer(file_buffer&& other);
|
||||||
|
FMT_API ~file_buffer();
|
||||||
|
|
||||||
|
void flush() {
|
||||||
|
if (size() == 0) return;
|
||||||
|
file_.write(data(), size() * sizeof(data()[0]));
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
flush();
|
||||||
|
file_.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
// Added {} below to work around default constructor error known to
|
// Added {} below to work around default constructor error known to
|
||||||
// occur in Xcode versions 7.2.1 and 8.2.1.
|
// occur in Xcode versions 7.2.1 and 8.2.1.
|
||||||
constexpr detail::buffer_size buffer_size{};
|
constexpr detail::buffer_size buffer_size{};
|
||||||
|
|
||||||
/** A fast output stream which is not thread-safe. */
|
/** A fast output stream which is not thread-safe. */
|
||||||
class FMT_API ostream final : private detail::buffer<char> {
|
class FMT_API ostream {
|
||||||
private:
|
private:
|
||||||
file file_;
|
FMT_MSC_WARNING(suppress : 4251)
|
||||||
|
detail::file_buffer buffer_;
|
||||||
void grow(size_t) override;
|
|
||||||
|
|
||||||
ostream(cstring_view path, const detail::ostream_params& params)
|
ostream(cstring_view path, const detail::ostream_params& params)
|
||||||
: file_(path, params.oflag) {
|
: buffer_(path, params) {}
|
||||||
set(new char[params.buffer_size], params.buffer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ostream(ostream&& other)
|
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
|
||||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
|
||||||
file_(std::move(other.file_)) {
|
|
||||||
other.clear();
|
|
||||||
other.set(nullptr, 0);
|
|
||||||
}
|
|
||||||
~ostream() {
|
|
||||||
flush();
|
|
||||||
delete[] data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void flush() {
|
~ostream();
|
||||||
if (size() == 0) return;
|
|
||||||
file_.write(data(), size());
|
void flush() { buffer_.flush(); }
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
friend ostream output_file(cstring_view path, T... params);
|
friend ostream output_file(cstring_view path, T... params);
|
||||||
|
|
||||||
void close() {
|
void close() { buffer_.close(); }
|
||||||
flush();
|
|
||||||
file_.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Formats ``args`` according to specifications in ``fmt`` and writes the
|
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||||
output to the file.
|
output to the file.
|
||||||
*/
|
*/
|
||||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||||
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
|
||||||
fmt::make_format_args(args...));
|
fmt::make_format_args(args...));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) {
|
||||||
}
|
}
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_END
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_OS_H_
|
#endif // FMT_OS_H_
|
||||||
|
|
|
@ -8,86 +8,65 @@
|
||||||
#ifndef FMT_OSTREAM_H_
|
#ifndef FMT_OSTREAM_H_
|
||||||
#define FMT_OSTREAM_H_
|
#define FMT_OSTREAM_H_
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream> // std::filebuf
|
||||||
#include <ostream>
|
|
||||||
|
#if defined(_WIN32) && defined(__GLIBCXX__)
|
||||||
|
# include <ext/stdio_filebuf.h>
|
||||||
|
# include <ext/stdio_sync_filebuf.h>
|
||||||
|
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||||
|
# include <__std_stream>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// Checks if T has a user-defined operator<<.
|
|
||||||
template <typename T, typename Char, typename Enable = void>
|
|
||||||
class is_streamable {
|
|
||||||
private:
|
|
||||||
template <typename U>
|
|
||||||
static auto test(int)
|
|
||||||
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
|
|
||||||
<< std::declval<U>()) != 0>;
|
|
||||||
|
|
||||||
template <typename> static auto test(...) -> std::false_type;
|
|
||||||
|
|
||||||
using result = decltype(test<T>(0));
|
|
||||||
|
|
||||||
public:
|
|
||||||
is_streamable() = default;
|
|
||||||
|
|
||||||
static const bool value = result::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Formatting of built-in types and arrays is intentionally disabled because
|
|
||||||
// it's handled by standard (non-ostream) formatters.
|
|
||||||
template <typename T, typename Char>
|
|
||||||
struct is_streamable<
|
|
||||||
T, Char,
|
|
||||||
enable_if_t<
|
|
||||||
std::is_arithmetic<T>::value || std::is_array<T>::value ||
|
|
||||||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
|
|
||||||
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
|
|
||||||
std::is_same<T, std_string_view<Char>>::value ||
|
|
||||||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
|
|
||||||
: std::false_type {};
|
|
||||||
|
|
||||||
template <typename Char> FILE* get_file(std::basic_filebuf<Char>&) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dummy_filebuf {
|
|
||||||
FILE* _Myfile;
|
|
||||||
};
|
|
||||||
template <typename T, typename U = int> struct ms_filebuf {
|
|
||||||
using type = dummy_filebuf;
|
|
||||||
};
|
|
||||||
template <typename T> struct ms_filebuf<T, decltype(T::_Myfile, 0)> {
|
|
||||||
using type = T;
|
|
||||||
};
|
|
||||||
using filebuf_type = ms_filebuf<std::filebuf>::type;
|
|
||||||
|
|
||||||
FILE* get_file(filebuf_type& buf);
|
|
||||||
|
|
||||||
// Generate a unique explicit instantion in every translation unit using a tag
|
// Generate a unique explicit instantion in every translation unit using a tag
|
||||||
// type in an anonymous namespace.
|
// type in an anonymous namespace.
|
||||||
namespace {
|
namespace {
|
||||||
struct filebuf_access_tag {};
|
struct file_access_tag {};
|
||||||
} // namespace
|
} // namespace
|
||||||
template <typename Tag, typename FileMemberPtr, FileMemberPtr file>
|
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
|
||||||
class filebuf_access {
|
class file_access {
|
||||||
friend FILE* get_file(filebuf_type& buf) { return buf.*file; }
|
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||||
};
|
};
|
||||||
template class filebuf_access<filebuf_access_tag,
|
|
||||||
decltype(&filebuf_type::_Myfile),
|
|
||||||
&filebuf_type::_Myfile>;
|
|
||||||
|
|
||||||
inline bool write(std::filebuf& buf, fmt::string_view data) {
|
#if FMT_MSC_VERSION
|
||||||
FILE* f = get_file(buf);
|
template class file_access<file_access_tag, std::filebuf,
|
||||||
if (!f) return false;
|
&std::filebuf::_Myfile>;
|
||||||
print(f, data);
|
auto get_file(std::filebuf&) -> FILE*;
|
||||||
return true;
|
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||||
|
template class file_access<file_access_tag, std::__stdoutbuf<char>,
|
||||||
|
&std::__stdoutbuf<char>::__file_>;
|
||||||
|
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
|
||||||
|
#if FMT_MSC_VERSION
|
||||||
|
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
||||||
|
if (FILE* f = get_file(*buf)) return write_console(f, data);
|
||||||
|
#elif defined(_WIN32) && defined(__GLIBCXX__)
|
||||||
|
auto* rdbuf = os.rdbuf();
|
||||||
|
FILE* c_file;
|
||||||
|
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||||
|
c_file = sfbuf->file();
|
||||||
|
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||||
|
c_file = fbuf->file();
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
if (c_file) return write_console(c_file, data);
|
||||||
|
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||||
|
if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf()))
|
||||||
|
if (FILE* f = get_file(*buf)) return write_console(f, data);
|
||||||
|
#else
|
||||||
|
ignore_unused(os, data);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) {
|
inline bool write_ostream_unicode(std::wostream&,
|
||||||
|
fmt::basic_string_view<wchar_t>) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,10 +74,6 @@ inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) {
|
||||||
// It is a separate function rather than a part of vprint to simplify testing.
|
// It is a separate function rather than a part of vprint to simplify testing.
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||||
if (const_check(FMT_MSC_VERSION)) {
|
|
||||||
auto filebuf = dynamic_cast<std::basic_filebuf<Char>*>(os.rdbuf());
|
|
||||||
if (filebuf && write(*filebuf, {buf.data(), buf.size()})) return;
|
|
||||||
}
|
|
||||||
const Char* buf_data = buf.data();
|
const Char* buf_data = buf.data();
|
||||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||||
unsigned_streamsize size = buf.size();
|
unsigned_streamsize size = buf.size();
|
||||||
|
@ -130,11 +105,13 @@ template <typename T> struct streamed_view { const T& value; };
|
||||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||||
|
void set_debug_format() = delete;
|
||||||
|
|
||||||
template <typename T, typename OutputIt>
|
template <typename T, typename OutputIt>
|
||||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
|
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
|
||||||
-> OutputIt {
|
-> OutputIt {
|
||||||
auto buffer = basic_memory_buffer<Char>();
|
auto buffer = basic_memory_buffer<Char>();
|
||||||
format_value(buffer, value, ctx.locale());
|
detail::format_value(buffer, value, ctx.locale());
|
||||||
return formatter<basic_string_view<Char>, Char>::format(
|
return formatter<basic_string_view<Char>, Char>::format(
|
||||||
{buffer.data(), buffer.size()}, ctx);
|
{buffer.data(), buffer.size()}, ctx);
|
||||||
}
|
}
|
||||||
|
@ -142,12 +119,13 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||||
|
|
||||||
using ostream_formatter = basic_ostream_formatter<char>;
|
using ostream_formatter = basic_ostream_formatter<char>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T, typename Char>
|
||||||
struct formatter<detail::streamed_view<T>> : ostream_formatter {
|
struct formatter<detail::streamed_view<T>, Char>
|
||||||
|
: basic_ostream_formatter<Char> {
|
||||||
template <typename OutputIt>
|
template <typename OutputIt>
|
||||||
auto format(detail::streamed_view<T> view,
|
auto format(detail::streamed_view<T> view,
|
||||||
basic_format_context<OutputIt, char>& ctx) const -> OutputIt {
|
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
|
||||||
return ostream_formatter::format(view.value, ctx);
|
return basic_ostream_formatter<Char>::format(view.value, ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -168,21 +146,22 @@ auto streamed(const T& value) -> detail::streamed_view<T> {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
inline void vprint_directly(std::ostream& os, string_view format_str,
|
||||||
template <typename T, typename Char>
|
format_args args) {
|
||||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
auto buffer = memory_buffer();
|
||||||
: basic_ostream_formatter<Char> {
|
detail::vformat_to(buffer, format_str, args);
|
||||||
using basic_ostream_formatter<Char>::format;
|
detail::write_buffer(os, buffer);
|
||||||
};
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
FMT_MODULE_EXPORT template <typename Char>
|
FMT_EXPORT template <typename Char>
|
||||||
void vprint(std::basic_ostream<Char>& os,
|
void vprint(std::basic_ostream<Char>& os,
|
||||||
basic_string_view<type_identity_t<Char>> format_str,
|
basic_string_view<type_identity_t<Char>> format_str,
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
auto buffer = basic_memory_buffer<Char>();
|
auto buffer = basic_memory_buffer<Char>();
|
||||||
detail::vformat_to(buffer, format_str, args);
|
detail::vformat_to(buffer, format_str, args);
|
||||||
|
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
|
||||||
detail::write_buffer(os, buffer);
|
detail::write_buffer(os, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,12 +174,16 @@ void vprint(std::basic_ostream<Char>& os,
|
||||||
fmt::print(cerr, "Don't {}!", "panic");
|
fmt::print(cerr, "Don't {}!", "panic");
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
FMT_MODULE_EXPORT template <typename... T>
|
FMT_EXPORT template <typename... T>
|
||||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||||
vprint(os, fmt, fmt::make_format_args(args...));
|
const auto& vargs = fmt::make_format_args(args...);
|
||||||
|
if (detail::is_utf8())
|
||||||
|
vprint(os, fmt, vargs);
|
||||||
|
else
|
||||||
|
detail::vprint_directly(os, fmt, vargs);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_MODULE_EXPORT
|
FMT_EXPORT
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void print(std::wostream& os,
|
void print(std::wostream& os,
|
||||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||||
|
@ -208,6 +191,19 @@ void print(std::wostream& os,
|
||||||
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
|
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FMT_EXPORT template <typename... T>
|
||||||
|
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||||
|
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename... Args>
|
||||||
|
void println(std::wostream& os,
|
||||||
|
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||||
|
Args&&... args) {
|
||||||
|
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_OSTREAM_H_
|
#endif // FMT_OSTREAM_H_
|
||||||
|
|
|
@ -14,24 +14,18 @@
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
FMT_MODULE_EXPORT_BEGIN
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char> class basic_printf_context {
|
||||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
|
||||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
|
||||||
private:
|
private:
|
||||||
OutputIt out_;
|
detail::buffer_appender<Char> out_;
|
||||||
basic_format_args<basic_printf_context> args_;
|
basic_format_args<basic_printf_context> args_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
using format_arg = basic_format_arg<basic_printf_context>;
|
using parse_context_type = basic_format_parse_context<Char>;
|
||||||
using parse_context_type = basic_printf_parse_context<Char>;
|
|
||||||
template <typename T> using formatter_type = printf_formatter<T>;
|
template <typename T> using formatter_type = printf_formatter<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,68 +34,68 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||||
stored in the context object so make sure they have appropriate lifetimes.
|
stored in the context object so make sure they have appropriate lifetimes.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
basic_printf_context(OutputIt out,
|
basic_printf_context(detail::buffer_appender<Char> out,
|
||||||
basic_format_args<basic_printf_context> args)
|
basic_format_args<basic_printf_context> args)
|
||||||
: out_(out), args_(args) {}
|
: out_(out), args_(args) {}
|
||||||
|
|
||||||
OutputIt out() { return out_; }
|
auto out() -> detail::buffer_appender<Char> { return out_; }
|
||||||
void advance_to(OutputIt it) { out_ = it; }
|
void advance_to(detail::buffer_appender<Char>) {}
|
||||||
|
|
||||||
detail::locale_ref locale() { return {}; }
|
auto locale() -> detail::locale_ref { return {}; }
|
||||||
|
|
||||||
format_arg arg(int id) const { return args_.get(id); }
|
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
||||||
|
return args_.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_error(const char* message) {
|
FMT_CONSTEXPR void on_error(const char* message) {
|
||||||
detail::error_handler().on_error(message);
|
detail::error_handler().on_error(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_BEGIN_DETAIL_NAMESPACE
|
namespace detail {
|
||||||
|
|
||||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||||
// signed and unsigned integers.
|
// signed and unsigned integers.
|
||||||
template <bool IsSigned> struct int_checker {
|
template <bool IsSigned> struct int_checker {
|
||||||
template <typename T> static bool fits_in_int(T value) {
|
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||||
unsigned max = max_value<int>();
|
unsigned max = max_value<int>();
|
||||||
return value <= max;
|
return value <= max;
|
||||||
}
|
}
|
||||||
static bool fits_in_int(bool) { return true; }
|
static auto fits_in_int(bool) -> bool { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct int_checker<true> {
|
template <> struct int_checker<true> {
|
||||||
template <typename T> static bool fits_in_int(T value) {
|
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||||
return value >= (std::numeric_limits<int>::min)() &&
|
return value >= (std::numeric_limits<int>::min)() &&
|
||||||
value <= max_value<int>();
|
value <= max_value<int>();
|
||||||
}
|
}
|
||||||
static bool fits_in_int(int) { return true; }
|
static auto fits_in_int(int) -> bool { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class printf_precision_handler {
|
struct printf_precision_handler {
|
||||||
public:
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
int operator()(T value) {
|
auto operator()(T value) -> int {
|
||||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||||
FMT_THROW(format_error("number is too big"));
|
throw_format_error("number is too big");
|
||||||
return (std::max)(static_cast<int>(value), 0);
|
return (std::max)(static_cast<int>(value), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
int operator()(T) {
|
auto operator()(T) -> int {
|
||||||
FMT_THROW(format_error("precision is not integer"));
|
throw_format_error("precision is not integer");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// An argument visitor that returns true iff arg is a zero integer.
|
// An argument visitor that returns true iff arg is a zero integer.
|
||||||
class is_zero_int {
|
struct is_zero_int {
|
||||||
public:
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
bool operator()(T value) {
|
auto operator()(T value) -> bool {
|
||||||
return value == 0;
|
return value == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
bool operator()(T) {
|
auto operator()(T) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -132,22 +126,23 @@ template <typename T, typename Context> class arg_converter {
|
||||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||||
// Extra casts are used to silence warnings.
|
// Extra casts are used to silence warnings.
|
||||||
if (is_signed) {
|
if (is_signed) {
|
||||||
arg_ = detail::make_arg<Context>(
|
auto n = static_cast<int>(static_cast<target_type>(value));
|
||||||
static_cast<int>(static_cast<target_type>(value)));
|
arg_ = detail::make_arg<Context>(n);
|
||||||
} else {
|
} else {
|
||||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||||
arg_ = detail::make_arg<Context>(
|
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
||||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
arg_ = detail::make_arg<Context>(n);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (is_signed) {
|
if (is_signed) {
|
||||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||||
// std::printf("%lld", -42); // prints "4294967254"
|
// std::printf("%lld", -42); // prints "4294967254"
|
||||||
// but we don't have to do the same because it's a UB.
|
// but we don't have to do the same because it's a UB.
|
||||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
auto n = static_cast<long long>(value);
|
||||||
|
arg_ = detail::make_arg<Context>(n);
|
||||||
} else {
|
} else {
|
||||||
arg_ = detail::make_arg<Context>(
|
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
||||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
arg_ = detail::make_arg<Context>(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,8 +170,8 @@ template <typename Context> class char_converter {
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
void operator()(T value) {
|
void operator()(T value) {
|
||||||
arg_ = detail::make_arg<Context>(
|
auto c = static_cast<typename Context::char_type>(value);
|
||||||
static_cast<typename Context::char_type>(value));
|
arg_ = detail::make_arg<Context>(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
|
@ -186,122 +181,131 @@ template <typename Context> class char_converter {
|
||||||
// An argument visitor that return a pointer to a C string if argument is a
|
// An argument visitor that return a pointer to a C string if argument is a
|
||||||
// string or null otherwise.
|
// string or null otherwise.
|
||||||
template <typename Char> struct get_cstring {
|
template <typename Char> struct get_cstring {
|
||||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
|
||||||
const Char* operator()(const Char* s) { return s; }
|
auto operator()(const Char* s) -> const Char* { return s; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Checks if an argument is a valid printf width specifier and sets
|
// Checks if an argument is a valid printf width specifier and sets
|
||||||
// left alignment if it is negative.
|
// left alignment if it is negative.
|
||||||
template <typename Char> class printf_width_handler {
|
template <typename Char> class printf_width_handler {
|
||||||
private:
|
private:
|
||||||
using format_specs = basic_format_specs<Char>;
|
format_specs<Char>& specs_;
|
||||||
|
|
||||||
format_specs& specs_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
unsigned operator()(T value) {
|
auto operator()(T value) -> unsigned {
|
||||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||||
if (detail::is_negative(value)) {
|
if (detail::is_negative(value)) {
|
||||||
specs_.align = align::left;
|
specs_.align = align::left;
|
||||||
width = 0 - width;
|
width = 0 - width;
|
||||||
}
|
}
|
||||||
unsigned int_max = max_value<int>();
|
unsigned int_max = max_value<int>();
|
||||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
if (width > int_max) throw_format_error("number is too big");
|
||||||
return static_cast<unsigned>(width);
|
return static_cast<unsigned>(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||||
unsigned operator()(T) {
|
auto operator()(T) -> unsigned {
|
||||||
FMT_THROW(format_error("width is not integer"));
|
throw_format_error("width is not integer");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Workaround for a bug with the XL compiler when initializing
|
||||||
|
// printf_arg_formatter's base class.
|
||||||
|
template <typename Char>
|
||||||
|
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
|
||||||
|
-> arg_formatter<Char> {
|
||||||
|
return {iter, s, locale_ref()};
|
||||||
|
}
|
||||||
|
|
||||||
// The ``printf`` argument formatter.
|
// The ``printf`` argument formatter.
|
||||||
template <typename OutputIt, typename Char>
|
template <typename Char>
|
||||||
class printf_arg_formatter : public arg_formatter<Char> {
|
class printf_arg_formatter : public arg_formatter<Char> {
|
||||||
private:
|
private:
|
||||||
using base = arg_formatter<Char>;
|
using base = arg_formatter<Char>;
|
||||||
using context_type = basic_printf_context<OutputIt, Char>;
|
using context_type = basic_printf_context<Char>;
|
||||||
using format_specs = basic_format_specs<Char>;
|
|
||||||
|
|
||||||
context_type& context_;
|
context_type& context_;
|
||||||
|
|
||||||
OutputIt write_null_pointer(bool is_string = false) {
|
void write_null_pointer(bool is_string = false) {
|
||||||
auto s = this->specs;
|
auto s = this->specs;
|
||||||
s.type = presentation_type::none;
|
s.type = presentation_type::none;
|
||||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
|
||||||
: base{iter, s, locale_ref()}, context_(ctx) {}
|
context_type& ctx)
|
||||||
|
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
||||||
|
|
||||||
OutputIt operator()(monostate value) { return base::operator()(value); }
|
void operator()(monostate value) { base::operator()(value); }
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||||
OutputIt operator()(T value) {
|
void operator()(T value) {
|
||||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||||
// std::is_same instead.
|
// std::is_same instead.
|
||||||
if (std::is_same<T, Char>::value) {
|
if (!std::is_same<T, Char>::value) {
|
||||||
format_specs fmt_specs = this->specs;
|
base::operator()(value);
|
||||||
if (fmt_specs.type != presentation_type::none &&
|
return;
|
||||||
fmt_specs.type != presentation_type::chr) {
|
|
||||||
return (*this)(static_cast<int>(value));
|
|
||||||
}
|
|
||||||
fmt_specs.sign = sign::none;
|
|
||||||
fmt_specs.alt = false;
|
|
||||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
|
||||||
// align::numeric needs to be overwritten here since the '0' flag is
|
|
||||||
// ignored for non-numeric types
|
|
||||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
|
||||||
fmt_specs.align = align::right;
|
|
||||||
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
|
||||||
}
|
}
|
||||||
return base::operator()(value);
|
format_specs<Char> fmt_specs = this->specs;
|
||||||
|
if (fmt_specs.type != presentation_type::none &&
|
||||||
|
fmt_specs.type != presentation_type::chr) {
|
||||||
|
return (*this)(static_cast<int>(value));
|
||||||
|
}
|
||||||
|
fmt_specs.sign = sign::none;
|
||||||
|
fmt_specs.alt = false;
|
||||||
|
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||||
|
// align::numeric needs to be overwritten here since the '0' flag is
|
||||||
|
// ignored for non-numeric types
|
||||||
|
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||||
|
fmt_specs.align = align::right;
|
||||||
|
write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||||
OutputIt operator()(T value) {
|
void operator()(T value) {
|
||||||
return base::operator()(value);
|
base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats a null-terminated C string. */
|
/** Formats a null-terminated C string. */
|
||||||
OutputIt operator()(const char* value) {
|
void operator()(const char* value) {
|
||||||
if (value) return base::operator()(value);
|
if (value)
|
||||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
base::operator()(value);
|
||||||
|
else
|
||||||
|
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats a null-terminated wide C string. */
|
/** Formats a null-terminated wide C string. */
|
||||||
OutputIt operator()(const wchar_t* value) {
|
void operator()(const wchar_t* value) {
|
||||||
if (value) return base::operator()(value);
|
if (value)
|
||||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
base::operator()(value);
|
||||||
|
else
|
||||||
|
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputIt operator()(basic_string_view<Char> value) {
|
void operator()(basic_string_view<Char> value) { base::operator()(value); }
|
||||||
return base::operator()(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Formats a pointer. */
|
/** Formats a pointer. */
|
||||||
OutputIt operator()(const void* value) {
|
void operator()(const void* value) {
|
||||||
return value ? base::operator()(value) : write_null_pointer();
|
if (value)
|
||||||
|
base::operator()(value);
|
||||||
|
else
|
||||||
|
write_null_pointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats an argument of a custom (user-defined) type. */
|
/** Formats an argument of a custom (user-defined) type. */
|
||||||
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||||
auto parse_ctx =
|
auto parse_ctx = basic_format_parse_context<Char>({});
|
||||||
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
|
||||||
handle.format(parse_ctx, context_);
|
handle.format(parse_ctx, context_);
|
||||||
return this->out;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
|
||||||
const Char* end) {
|
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
switch (*it) {
|
switch (*it) {
|
||||||
case '-':
|
case '-':
|
||||||
|
@ -314,9 +318,7 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||||
specs.fill[0] = '0';
|
specs.fill[0] = '0';
|
||||||
break;
|
break;
|
||||||
case ' ':
|
case ' ':
|
||||||
if (specs.sign != sign::plus) {
|
if (specs.sign != sign::plus) specs.sign = sign::space;
|
||||||
specs.sign = sign::space;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case '#':
|
case '#':
|
||||||
specs.alt = true;
|
specs.alt = true;
|
||||||
|
@ -328,8 +330,8 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename GetArg>
|
template <typename Char, typename GetArg>
|
||||||
int parse_header(const Char*& it, const Char* end,
|
auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
|
||||||
basic_format_specs<Char>& specs, GetArg get_arg) {
|
GetArg get_arg) -> int {
|
||||||
int arg_index = -1;
|
int arg_index = -1;
|
||||||
Char c = *it;
|
Char c = *it;
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
|
@ -344,7 +346,7 @@ int parse_header(const Char*& it, const Char* end,
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
// Nonzero value means that we parsed width and don't need to
|
// Nonzero value means that we parsed width and don't need to
|
||||||
// parse it or flags again, so return now.
|
// parse it or flags again, so return now.
|
||||||
if (value == -1) FMT_THROW(format_error("number is too big"));
|
if (value == -1) throw_format_error("number is too big");
|
||||||
specs.width = value;
|
specs.width = value;
|
||||||
return arg_index;
|
return arg_index;
|
||||||
}
|
}
|
||||||
|
@ -355,7 +357,7 @@ int parse_header(const Char*& it, const Char* end,
|
||||||
if (it != end) {
|
if (it != end) {
|
||||||
if (*it >= '0' && *it <= '9') {
|
if (*it >= '0' && *it <= '9') {
|
||||||
specs.width = parse_nonnegative_int(it, end, -1);
|
specs.width = parse_nonnegative_int(it, end, -1);
|
||||||
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
|
if (specs.width == -1) throw_format_error("number is too big");
|
||||||
} else if (*it == '*') {
|
} else if (*it == '*') {
|
||||||
++it;
|
++it;
|
||||||
specs.width = static_cast<int>(visit_format_arg(
|
specs.width = static_cast<int>(visit_format_arg(
|
||||||
|
@ -365,13 +367,53 @@ int parse_header(const Char*& it, const Char* end,
|
||||||
return arg_index;
|
return arg_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline auto parse_printf_presentation_type(char c, type t)
|
||||||
|
-> presentation_type {
|
||||||
|
using pt = presentation_type;
|
||||||
|
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
|
||||||
|
switch (c) {
|
||||||
|
case 'd':
|
||||||
|
return in(t, integral_set) ? pt::dec : pt::none;
|
||||||
|
case 'o':
|
||||||
|
return in(t, integral_set) ? pt::oct : pt::none;
|
||||||
|
case 'x':
|
||||||
|
return in(t, integral_set) ? pt::hex_lower : pt::none;
|
||||||
|
case 'X':
|
||||||
|
return in(t, integral_set) ? pt::hex_upper : pt::none;
|
||||||
|
case 'a':
|
||||||
|
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
|
||||||
|
case 'A':
|
||||||
|
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
|
||||||
|
case 'e':
|
||||||
|
return in(t, float_set) ? pt::exp_lower : pt::none;
|
||||||
|
case 'E':
|
||||||
|
return in(t, float_set) ? pt::exp_upper : pt::none;
|
||||||
|
case 'f':
|
||||||
|
return in(t, float_set) ? pt::fixed_lower : pt::none;
|
||||||
|
case 'F':
|
||||||
|
return in(t, float_set) ? pt::fixed_upper : pt::none;
|
||||||
|
case 'g':
|
||||||
|
return in(t, float_set) ? pt::general_lower : pt::none;
|
||||||
|
case 'G':
|
||||||
|
return in(t, float_set) ? pt::general_upper : pt::none;
|
||||||
|
case 'c':
|
||||||
|
return in(t, integral_set) ? pt::chr : pt::none;
|
||||||
|
case 's':
|
||||||
|
return in(t, string_set | cstring_set) ? pt::string : pt::none;
|
||||||
|
case 'p':
|
||||||
|
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
|
||||||
|
default:
|
||||||
|
return pt::none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char, typename Context>
|
template <typename Char, typename Context>
|
||||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
basic_format_args<Context> args) {
|
basic_format_args<Context> args) {
|
||||||
using OutputIt = buffer_appender<Char>;
|
using iterator = buffer_appender<Char>;
|
||||||
auto out = OutputIt(buf);
|
auto out = iterator(buf);
|
||||||
auto context = basic_printf_context<OutputIt, Char>(out, args);
|
auto context = basic_printf_context<Char>(out, args);
|
||||||
auto parse_ctx = basic_printf_parse_context<Char>(format);
|
auto parse_ctx = basic_format_parse_context<Char>(format);
|
||||||
|
|
||||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||||
// argument.
|
// argument.
|
||||||
|
@ -387,26 +429,24 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
const Char* end = parse_ctx.end();
|
const Char* end = parse_ctx.end();
|
||||||
auto it = start;
|
auto it = start;
|
||||||
while (it != end) {
|
while (it != end) {
|
||||||
if (!detail::find<false, Char>(it, end, '%', it)) {
|
if (!find<false, Char>(it, end, '%', it)) {
|
||||||
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
it = end; // find leaves it == nullptr if it doesn't find '%'.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Char c = *it++;
|
Char c = *it++;
|
||||||
if (it != end && *it == c) {
|
if (it != end && *it == c) {
|
||||||
out = detail::write(
|
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||||
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
|
||||||
start = ++it;
|
start = ++it;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
out = detail::write(out, basic_string_view<Char>(
|
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||||
start, detail::to_unsigned(it - 1 - start)));
|
|
||||||
|
|
||||||
basic_format_specs<Char> specs;
|
auto specs = format_specs<Char>();
|
||||||
specs.align = align::right;
|
specs.align = align::right;
|
||||||
|
|
||||||
// Parse argument index, flags and width.
|
// Parse argument index, flags and width.
|
||||||
int arg_index = parse_header(it, end, specs, get_arg);
|
int arg_index = parse_header(it, end, specs, get_arg);
|
||||||
if (arg_index == 0) parse_ctx.on_error("argument not found");
|
if (arg_index == 0) throw_format_error("argument not found");
|
||||||
|
|
||||||
// Parse precision.
|
// Parse precision.
|
||||||
if (it != end && *it == '.') {
|
if (it != end && *it == '.') {
|
||||||
|
@ -417,7 +457,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
} else if (c == '*') {
|
} else if (c == '*') {
|
||||||
++it;
|
++it;
|
||||||
specs.precision = static_cast<int>(
|
specs.precision = static_cast<int>(
|
||||||
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
|
visit_format_arg(printf_precision_handler(), get_arg(-1)));
|
||||||
} else {
|
} else {
|
||||||
specs.precision = 0;
|
specs.precision = 0;
|
||||||
}
|
}
|
||||||
|
@ -426,20 +466,19 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
auto arg = get_arg(arg_index);
|
auto arg = get_arg(arg_index);
|
||||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||||
// specified, the '0' flag is ignored
|
// specified, the '0' flag is ignored
|
||||||
if (specs.precision >= 0 && arg.is_integral())
|
if (specs.precision >= 0 && arg.is_integral()) {
|
||||||
specs.fill[0] =
|
// Ignore '0' for non-numeric types or if '-' present.
|
||||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
specs.fill[0] = ' ';
|
||||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
}
|
||||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
|
||||||
|
auto str = visit_format_arg(get_cstring<Char>(), arg);
|
||||||
auto str_end = str + specs.precision;
|
auto str_end = str + specs.precision;
|
||||||
auto nul = std::find(str, str_end, Char());
|
auto nul = std::find(str, str_end, Char());
|
||||||
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
|
auto sv = basic_string_view<Char>(
|
||||||
basic_string_view<Char>(
|
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
||||||
str, detail::to_unsigned(nul != str_end ? nul - str
|
arg = make_arg<basic_printf_context<Char>>(sv);
|
||||||
: specs.precision)));
|
|
||||||
}
|
}
|
||||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
|
||||||
specs.alt = false;
|
|
||||||
if (specs.fill[0] == '0') {
|
if (specs.fill[0] == '0') {
|
||||||
if (arg.is_arithmetic() && specs.align != align::left)
|
if (arg.is_arithmetic() && specs.align != align::left)
|
||||||
specs.align = align::numeric;
|
specs.align = align::numeric;
|
||||||
|
@ -451,7 +490,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
// Parse length and convert the argument to the required type.
|
// Parse length and convert the argument to the required type.
|
||||||
c = it != end ? *it++ : 0;
|
c = it != end ? *it++ : 0;
|
||||||
Char t = it != end ? *it : 0;
|
Char t = it != end ? *it : 0;
|
||||||
using detail::convert_arg;
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'h':
|
case 'h':
|
||||||
if (t == 'h') {
|
if (t == 'h') {
|
||||||
|
@ -490,7 +528,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse type.
|
// Parse type.
|
||||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
if (it == end) throw_format_error("invalid format string");
|
||||||
char type = static_cast<char>(*it++);
|
char type = static_cast<char>(*it++);
|
||||||
if (arg.is_integral()) {
|
if (arg.is_integral()) {
|
||||||
// Normalize type.
|
// Normalize type.
|
||||||
|
@ -500,32 +538,25 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
type = 'd';
|
type = 'd';
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
visit_format_arg(
|
visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
|
||||||
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
|
|
||||||
arg);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
specs.type = parse_presentation_type(type);
|
specs.type = parse_printf_presentation_type(type, arg.type());
|
||||||
if (specs.type == presentation_type::none)
|
if (specs.type == presentation_type::none)
|
||||||
parse_ctx.on_error("invalid type specifier");
|
throw_format_error("invalid format specifier");
|
||||||
|
|
||||||
start = it;
|
start = it;
|
||||||
|
|
||||||
// Format argument.
|
// Format argument.
|
||||||
out = visit_format_arg(
|
visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
|
||||||
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
|
|
||||||
}
|
}
|
||||||
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||||
}
|
}
|
||||||
FMT_END_DETAIL_NAMESPACE
|
} // namespace detail
|
||||||
|
|
||||||
template <typename Char>
|
using printf_context = basic_printf_context<char>;
|
||||||
using basic_printf_context_t =
|
using wprintf_context = basic_printf_context<wchar_t>;
|
||||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
|
||||||
|
|
||||||
using printf_context = basic_printf_context_t<char>;
|
|
||||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
|
||||||
|
|
||||||
using printf_args = basic_format_args<printf_context>;
|
using printf_args = basic_format_args<printf_context>;
|
||||||
using wprintf_args = basic_format_args<wprintf_context>;
|
using wprintf_args = basic_format_args<wprintf_context>;
|
||||||
|
@ -542,26 +573,21 @@ inline auto make_printf_args(const T&... args)
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// DEPRECATED!
|
||||||
\rst
|
|
||||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
|
||||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
inline auto make_wprintf_args(const T&... args)
|
inline auto make_wprintf_args(const T&... args)
|
||||||
-> format_arg_store<wprintf_context, T...> {
|
-> format_arg_store<wprintf_context, T...> {
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename Char>
|
||||||
inline auto vsprintf(
|
inline auto vsprintf(
|
||||||
const S& fmt,
|
basic_string_view<Char> fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
basic_memory_buffer<Char> buffer;
|
auto buf = basic_memory_buffer<Char>();
|
||||||
vprintf(buffer, detail::to_string_view(fmt), args);
|
detail::vprintf(buf, fmt, args);
|
||||||
return to_string(buffer);
|
return to_string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -576,20 +602,19 @@ inline auto vsprintf(
|
||||||
template <typename S, typename... T,
|
template <typename S, typename... T,
|
||||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||||
using context = basic_printf_context_t<Char>;
|
|
||||||
return vsprintf(detail::to_string_view(fmt),
|
return vsprintf(detail::to_string_view(fmt),
|
||||||
fmt::make_format_args<context>(args...));
|
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename Char>
|
||||||
inline auto vfprintf(
|
inline auto vfprintf(
|
||||||
std::FILE* f, const S& fmt,
|
std::FILE* f, basic_string_view<Char> fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||||
-> int {
|
-> int {
|
||||||
basic_memory_buffer<Char> buffer;
|
auto buf = basic_memory_buffer<Char>();
|
||||||
vprintf(buffer, detail::to_string_view(fmt), args);
|
detail::vprintf(buf, fmt, args);
|
||||||
size_t size = buffer.size();
|
size_t size = buf.size();
|
||||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
|
||||||
? -1
|
? -1
|
||||||
: static_cast<int>(size);
|
: static_cast<int>(size);
|
||||||
}
|
}
|
||||||
|
@ -605,17 +630,16 @@ inline auto vfprintf(
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... T, typename Char = char_t<S>>
|
template <typename S, typename... T, typename Char = char_t<S>>
|
||||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||||
using context = basic_printf_context_t<Char>;
|
|
||||||
return vfprintf(f, detail::to_string_view(fmt),
|
return vfprintf(f, detail::to_string_view(fmt),
|
||||||
fmt::make_format_args<context>(args...));
|
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename Char>
|
||||||
inline auto vprintf(
|
FMT_DEPRECATED inline auto vprintf(
|
||||||
const S& fmt,
|
basic_string_view<Char> fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||||
-> int {
|
-> int {
|
||||||
return vfprintf(stdout, detail::to_string_view(fmt), args);
|
return vfprintf(stdout, fmt, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -627,14 +651,17 @@ inline auto vprintf(
|
||||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
template <typename... T>
|
||||||
inline auto printf(const S& fmt, const T&... args) -> int {
|
inline auto printf(string_view fmt, const T&... args) -> int {
|
||||||
return vprintf(
|
return vfprintf(stdout, fmt, make_printf_args(args...));
|
||||||
detail::to_string_view(fmt),
|
}
|
||||||
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
template <typename... T>
|
||||||
|
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
|
||||||
|
const T&... args) -> int {
|
||||||
|
return vfprintf(stdout, fmt, make_wprintf_args(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_END
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_PRINTF_H_
|
#endif // FMT_PRINTF_H_
|
||||||
|
|
|
@ -22,27 +22,25 @@ FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename RangeT, typename OutputIterator>
|
template <typename Range, typename OutputIt>
|
||||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
auto copy(const Range& range, OutputIt out) -> OutputIt {
|
||||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||||
*out++ = *it;
|
*out++ = *it;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIterator>
|
template <typename OutputIt>
|
||||||
OutputIterator copy(const char* str, OutputIterator out) {
|
auto copy(const char* str, OutputIt out) -> OutputIt {
|
||||||
while (*str) *out++ = *str++;
|
while (*str) *out++ = *str++;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIterator>
|
template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
|
||||||
OutputIterator copy(char ch, OutputIterator out) {
|
|
||||||
*out++ = ch;
|
*out++ = ch;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIterator>
|
template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
|
||||||
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
|
||||||
*out++ = ch;
|
*out++ = ch;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +67,7 @@ template <typename T> class is_map {
|
||||||
template <typename> static void check(...);
|
template <typename> static void check(...);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
#ifdef FMT_FORMAT_MAP_AS_LIST
|
#ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
|
||||||
static constexpr const bool value = false;
|
static constexpr const bool value = false;
|
||||||
#else
|
#else
|
||||||
static constexpr const bool value =
|
static constexpr const bool value =
|
||||||
|
@ -82,7 +80,7 @@ template <typename T> class is_set {
|
||||||
template <typename> static void check(...);
|
template <typename> static void check(...);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
#ifdef FMT_FORMAT_SET_AS_LIST
|
#ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
|
||||||
static constexpr const bool value = false;
|
static constexpr const bool value = false;
|
||||||
#else
|
#else
|
||||||
static constexpr const bool value =
|
static constexpr const bool value =
|
||||||
|
@ -157,8 +155,9 @@ template <typename T>
|
||||||
struct has_mutable_begin_end<
|
struct has_mutable_begin_end<
|
||||||
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||||
decltype(detail::range_end(std::declval<T>())),
|
decltype(detail::range_end(std::declval<T>())),
|
||||||
enable_if_t<std::is_copy_constructible<T>::value>>>
|
// the extra int here is because older versions of MSVC don't
|
||||||
: std::true_type {};
|
// SFINAE properly unless there are distinct types
|
||||||
|
int>> : std::true_type {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_range_<T, void>
|
struct is_range_<T, void>
|
||||||
|
@ -211,41 +210,61 @@ class is_tuple_formattable_ {
|
||||||
static constexpr const bool value = false;
|
static constexpr const bool value = false;
|
||||||
};
|
};
|
||||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||||
template <std::size_t... I>
|
template <std::size_t... Is>
|
||||||
static std::true_type check2(index_sequence<I...>,
|
static std::true_type check2(index_sequence<Is...>,
|
||||||
integer_sequence<bool, (I == I)...>);
|
integer_sequence<bool, (Is == Is)...>);
|
||||||
static std::false_type check2(...);
|
static std::false_type check2(...);
|
||||||
template <std::size_t... I>
|
template <std::size_t... Is>
|
||||||
static decltype(check2(
|
static decltype(check2(
|
||||||
index_sequence<I...>{},
|
index_sequence<Is...>{},
|
||||||
integer_sequence<
|
integer_sequence<
|
||||||
bool, (is_formattable<typename std::tuple_element<I, T>::type,
|
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
|
||||||
C>::value)...>{})) check(index_sequence<I...>);
|
C>::value)...>{})) check(index_sequence<Is...>);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value =
|
static constexpr const bool value =
|
||||||
decltype(check(tuple_index_sequence<T>{}))::value;
|
decltype(check(tuple_index_sequence<T>{}))::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class Tuple, class F, size_t... Is>
|
template <typename Tuple, typename F, size_t... Is>
|
||||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept {
|
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
|
||||||
using std::get;
|
using std::get;
|
||||||
// using free function get<I>(T) now.
|
// Using a free function get<Is>(Tuple) now.
|
||||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
|
||||||
(void)_; // blocks warnings
|
ignore_unused(unused);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <typename Tuple, typename F>
|
||||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
|
||||||
T const&) {
|
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
|
||||||
return {};
|
std::forward<Tuple>(t), std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
|
||||||
const auto indexes = get_indexes(tup);
|
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
|
||||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
using std::get;
|
||||||
|
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
|
||||||
|
ignore_unused(unused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Tuple1, typename Tuple2, typename F>
|
||||||
|
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
|
||||||
|
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
|
||||||
|
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
|
||||||
|
std::forward<F>(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace tuple {
|
||||||
|
// Workaround a bug in MSVC 2019 (v140).
|
||||||
|
template <typename Char, typename... T>
|
||||||
|
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
|
||||||
|
|
||||||
|
using std::get;
|
||||||
|
template <typename Tuple, typename Char, std::size_t... Is>
|
||||||
|
auto get_formatters(index_sequence<Is...>)
|
||||||
|
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
|
||||||
|
} // namespace tuple
|
||||||
|
|
||||||
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
|
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
|
||||||
// Older MSVC doesn't get the reference type correctly for arrays.
|
// Older MSVC doesn't get the reference type correctly for arrays.
|
||||||
template <typename R> struct range_reference_type_impl {
|
template <typename R> struct range_reference_type_impl {
|
||||||
|
@ -269,45 +288,37 @@ using range_reference_type =
|
||||||
template <typename Range>
|
template <typename Range>
|
||||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||||
|
|
||||||
template <typename Range>
|
template <typename Formatter>
|
||||||
using uncvref_first_type = remove_cvref_t<
|
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
|
||||||
decltype(std::declval<range_reference_type<Range>>().first)>;
|
-> decltype(f.set_debug_format(set)) {
|
||||||
|
f.set_debug_format(set);
|
||||||
template <typename Range>
|
|
||||||
using uncvref_second_type = remove_cvref_t<
|
|
||||||
decltype(std::declval<range_reference_type<Range>>().second)>;
|
|
||||||
|
|
||||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
|
||||||
*out++ = ',';
|
|
||||||
*out++ = ' ';
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
template <typename Formatter>
|
||||||
|
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
// These are not generic lambdas for compatibility with C++11.
|
||||||
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
|
template <typename ParseContext> struct parse_empty_specs {
|
||||||
return write_escaped_string(out, str);
|
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
|
||||||
}
|
f.parse(ctx);
|
||||||
|
detail::maybe_set_debug_format(f, true);
|
||||||
|
}
|
||||||
|
ParseContext& ctx;
|
||||||
|
};
|
||||||
|
template <typename FormatContext> struct format_tuple_element {
|
||||||
|
using char_type = typename FormatContext::char_type;
|
||||||
|
|
||||||
template <typename Char, typename OutputIt, typename T,
|
template <typename T>
|
||||||
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
|
void operator()(const formatter<T, char_type>& f, const T& v) {
|
||||||
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
|
if (i > 0)
|
||||||
auto sv = std_string_view<Char>(str);
|
ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
|
||||||
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
|
ctx.advance_to(f.format(v, ctx));
|
||||||
}
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt, typename Arg,
|
int i;
|
||||||
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
FormatContext& ctx;
|
||||||
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
basic_string_view<char_type> separator;
|
||||||
return write_escaped_char(out, v);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
template <
|
|
||||||
typename Char, typename OutputIt, typename Arg,
|
|
||||||
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
|
||||||
!std::is_same<Arg, Char>::value)>
|
|
||||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
|
||||||
return write<Char>(out, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
@ -321,45 +332,58 @@ template <typename T, typename C> struct is_tuple_formattable {
|
||||||
detail::is_tuple_formattable_<T, C>::value;
|
detail::is_tuple_formattable_<T, C>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename TupleT, typename Char>
|
template <typename Tuple, typename Char>
|
||||||
struct formatter<TupleT, Char,
|
struct formatter<Tuple, Char,
|
||||||
enable_if_t<fmt::is_tuple_like<TupleT>::value &&
|
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
|
||||||
fmt::is_tuple_formattable<TupleT, Char>::value>> {
|
fmt::is_tuple_formattable<Tuple, Char>::value>> {
|
||||||
private:
|
private:
|
||||||
// C++11 generic lambda for format().
|
decltype(detail::tuple::get_formatters<Tuple, Char>(
|
||||||
template <typename FormatContext> struct format_each {
|
detail::tuple_index_sequence<Tuple>())) formatters_;
|
||||||
template <typename T> void operator()(const T& v) {
|
|
||||||
if (i > 0) out = detail::write_delimiter(out);
|
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||||
out = detail::write_range_entry<Char>(out, v);
|
basic_string_view<Char> opening_bracket_ =
|
||||||
++i;
|
detail::string_literal<Char, '('>{};
|
||||||
}
|
basic_string_view<Char> closing_bracket_ =
|
||||||
int i;
|
detail::string_literal<Char, ')'>{};
|
||||||
typename FormatContext::iterator& out;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template <typename ParseContext>
|
FMT_CONSTEXPR formatter() {}
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
|
||||||
|
separator_ = sep;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext = format_context>
|
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
|
||||||
auto format(const TupleT& values, FormatContext& ctx) const
|
basic_string_view<Char> close) {
|
||||||
|
opening_bracket_ = open;
|
||||||
|
closing_bracket_ = close;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
auto it = ctx.begin();
|
||||||
|
if (it != ctx.end() && *it != '}')
|
||||||
|
FMT_THROW(format_error("invalid format specifier"));
|
||||||
|
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const Tuple& value, FormatContext& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
auto out = ctx.out();
|
ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
|
||||||
*out++ = '(';
|
detail::for_each2(
|
||||||
detail::for_each(values, format_each<FormatContext>{0, out});
|
formatters_, value,
|
||||||
*out++ = ')';
|
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
|
||||||
return out;
|
return detail::copy_str<Char>(closing_bracket_, ctx.out());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename Char> struct is_range {
|
template <typename T, typename Char> struct is_range {
|
||||||
static constexpr const bool value =
|
static constexpr const bool value =
|
||||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||||
!detail::is_map<T>::value &&
|
|
||||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
!std::is_convertible<T, detail::std_string_view<Char>>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
@ -380,126 +404,171 @@ template <typename Context> struct range_mapper {
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Element>
|
template <typename Char, typename Element>
|
||||||
using range_formatter_type = conditional_t<
|
using range_formatter_type =
|
||||||
is_formattable<Element, Char>::value,
|
|
||||||
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
|
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
|
||||||
std::declval<Element>()))>,
|
std::declval<Element>()))>,
|
||||||
Char>,
|
Char>;
|
||||||
fallback_formatter<Element, Char>>;
|
|
||||||
|
|
||||||
template <typename R>
|
template <typename R>
|
||||||
using maybe_const_range =
|
using maybe_const_range =
|
||||||
conditional_t<has_const_begin_end<R>::value, const R, R>;
|
conditional_t<has_const_begin_end<R>::value, const R, R>;
|
||||||
|
|
||||||
|
// Workaround a bug in MSVC 2015 and earlier.
|
||||||
|
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||||
|
template <typename R, typename Char>
|
||||||
|
struct is_formattable_delayed
|
||||||
|
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
|
||||||
|
#endif
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename R, typename Char>
|
template <typename T, typename Char, typename Enable = void>
|
||||||
struct formatter<
|
struct range_formatter;
|
||||||
R, Char,
|
|
||||||
enable_if_t<
|
|
||||||
conjunction<fmt::is_range<R, Char>
|
|
||||||
// Workaround a bug in MSVC 2017 and earlier.
|
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
|
|
||||||
,
|
|
||||||
disjunction<
|
|
||||||
is_formattable<detail::uncvref_type<detail::maybe_const_range<R>>,
|
|
||||||
Char>,
|
|
||||||
detail::has_fallback_formatter<
|
|
||||||
detail::uncvref_type<detail::maybe_const_range<R>>, Char>
|
|
||||||
>
|
|
||||||
#endif
|
|
||||||
>::value
|
|
||||||
>> {
|
|
||||||
|
|
||||||
using range_type = detail::maybe_const_range<R>;
|
template <typename T, typename Char>
|
||||||
using formatter_type =
|
struct range_formatter<
|
||||||
detail::range_formatter_type<Char, detail::uncvref_type<range_type>>;
|
T, Char,
|
||||||
formatter_type underlying_;
|
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
|
||||||
bool custom_specs_ = false;
|
is_formattable<T, Char>>::value>> {
|
||||||
|
private:
|
||||||
|
detail::range_formatter_type<Char, T> underlying_;
|
||||||
|
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||||
|
basic_string_view<Char> opening_bracket_ =
|
||||||
|
detail::string_literal<Char, '['>{};
|
||||||
|
basic_string_view<Char> closing_bracket_ =
|
||||||
|
detail::string_literal<Char, ']'>{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR range_formatter() {}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
|
||||||
|
return underlying_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
|
||||||
|
separator_ = sep;
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
|
||||||
|
basic_string_view<Char> close) {
|
||||||
|
opening_bracket_ = open;
|
||||||
|
closing_bracket_ = close;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
auto end = ctx.end();
|
auto end = ctx.end();
|
||||||
if (it == end || *it == '}') return it;
|
|
||||||
|
|
||||||
if (*it != ':')
|
if (it != end && *it == 'n') {
|
||||||
FMT_THROW(format_error("no top-level range formatters supported"));
|
set_brackets({}, {});
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it != end && *it != '}') {
|
||||||
|
if (*it != ':') FMT_THROW(format_error("invalid format specifier"));
|
||||||
|
++it;
|
||||||
|
} else {
|
||||||
|
detail::maybe_set_debug_format(underlying_, true);
|
||||||
|
}
|
||||||
|
|
||||||
custom_specs_ = true;
|
|
||||||
++it;
|
|
||||||
ctx.advance_to(it);
|
ctx.advance_to(it);
|
||||||
return underlying_.parse(ctx);
|
return underlying_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename R, typename FormatContext>
|
||||||
|
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
|
detail::range_mapper<buffer_context<Char>> mapper;
|
||||||
|
auto out = ctx.out();
|
||||||
|
out = detail::copy_str<Char>(opening_bracket_, out);
|
||||||
|
int i = 0;
|
||||||
|
auto it = detail::range_begin(range);
|
||||||
|
auto end = detail::range_end(range);
|
||||||
|
for (; it != end; ++it) {
|
||||||
|
if (i > 0) out = detail::copy_str<Char>(separator_, out);
|
||||||
|
ctx.advance_to(out);
|
||||||
|
out = underlying_.format(mapper.map(*it), ctx);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
out = detail::copy_str<Char>(closing_bracket_, out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class range_format { disabled, map, set, sequence, string, debug_string };
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
struct range_format_kind_
|
||||||
|
: std::integral_constant<range_format,
|
||||||
|
std::is_same<uncvref_type<T>, T>::value
|
||||||
|
? range_format::disabled
|
||||||
|
: is_map<T>::value ? range_format::map
|
||||||
|
: is_set<T>::value ? range_format::set
|
||||||
|
: range_format::sequence> {};
|
||||||
|
|
||||||
|
template <range_format K, typename R, typename Char, typename Enable = void>
|
||||||
|
struct range_default_formatter;
|
||||||
|
|
||||||
|
template <range_format K>
|
||||||
|
using range_format_constant = std::integral_constant<range_format, K>;
|
||||||
|
|
||||||
|
template <range_format K, typename R, typename Char>
|
||||||
|
struct range_default_formatter<
|
||||||
|
K, R, Char,
|
||||||
|
enable_if_t<(K == range_format::sequence || K == range_format::map ||
|
||||||
|
K == range_format::set)>> {
|
||||||
|
using range_type = detail::maybe_const_range<R>;
|
||||||
|
range_formatter<detail::uncvref_type<range_type>, Char> underlying_;
|
||||||
|
|
||||||
|
FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); }
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void init(range_format_constant<range_format::set>) {
|
||||||
|
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
|
||||||
|
detail::string_literal<Char, '}'>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void init(range_format_constant<range_format::map>) {
|
||||||
|
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
|
||||||
|
detail::string_literal<Char, '}'>{});
|
||||||
|
underlying_.underlying().set_brackets({}, {});
|
||||||
|
underlying_.underlying().set_separator(
|
||||||
|
detail::string_literal<Char, ':', ' '>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {}
|
||||||
|
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return underlying_.parse(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(range_type& range, FormatContext& ctx) const
|
auto format(range_type& range, FormatContext& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
Char prefix = detail::is_set<R>::value ? '{' : '[';
|
return underlying_.format(range, ctx);
|
||||||
Char postfix = detail::is_set<R>::value ? '}' : ']';
|
|
||||||
detail::range_mapper<buffer_context<Char>> mapper;
|
|
||||||
auto out = ctx.out();
|
|
||||||
*out++ = prefix;
|
|
||||||
int i = 0;
|
|
||||||
auto it = detail::range_begin(range);
|
|
||||||
auto end = detail::range_end(range);
|
|
||||||
for (; it != end; ++it) {
|
|
||||||
if (i > 0) out = detail::write_delimiter(out);
|
|
||||||
if (custom_specs_) {
|
|
||||||
ctx.advance_to(out);
|
|
||||||
out = underlying_.format(mapper.map(*it), ctx);
|
|
||||||
} else {
|
|
||||||
out = detail::write_range_entry<Char>(out, *it);
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
*out++ = postfix;
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char, typename Enable = void>
|
||||||
|
struct range_format_kind
|
||||||
|
: conditional_t<
|
||||||
|
is_range<T, Char>::value, detail::range_format_kind_<T>,
|
||||||
|
std::integral_constant<range_format, range_format::disabled>> {};
|
||||||
|
|
||||||
|
template <typename R, typename Char>
|
||||||
struct formatter<
|
struct formatter<
|
||||||
T, Char,
|
R, Char,
|
||||||
enable_if_t<conjunction<detail::is_map<T>
|
enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value !=
|
||||||
// Workaround a bug in MSVC 2017 and earlier.
|
range_format::disabled>
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
|
// Workaround a bug in MSVC 2015 and earlier.
|
||||||
,
|
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||||
disjunction<
|
,
|
||||||
is_formattable<detail::uncvref_first_type<T>, Char>,
|
detail::is_formattable_delayed<R, Char>
|
||||||
detail::has_fallback_formatter<detail::uncvref_first_type<T>, Char>
|
|
||||||
>,
|
|
||||||
disjunction<
|
|
||||||
is_formattable<detail::uncvref_second_type<T>, Char>,
|
|
||||||
detail::has_fallback_formatter<detail::uncvref_second_type<T>, Char>
|
|
||||||
>
|
|
||||||
#endif
|
#endif
|
||||||
>::value
|
>::value>>
|
||||||
>> {
|
: detail::range_default_formatter<range_format_kind<R, Char>::value, R,
|
||||||
template <typename ParseContext>
|
Char> {
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <
|
|
||||||
typename FormatContext, typename U,
|
|
||||||
FMT_ENABLE_IF(
|
|
||||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
|
|
||||||
const T, T>>::value)>
|
|
||||||
auto format(U& map, FormatContext& ctx) const -> decltype(ctx.out()) {
|
|
||||||
auto out = ctx.out();
|
|
||||||
*out++ = '{';
|
|
||||||
int i = 0;
|
|
||||||
for (const auto& item : map) {
|
|
||||||
if (i > 0) out = detail::write_delimiter(out);
|
|
||||||
out = detail::write_range_entry<Char>(out, item.first);
|
|
||||||
*out++ = ':';
|
|
||||||
*out++ = ' ';
|
|
||||||
out = detail::write_range_entry<Char>(out, item.second);
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
*out++ = '}';
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||||
|
@ -510,9 +579,6 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||||
: tuple(t), sep{s} {}
|
: tuple(t), sep{s} {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename... T>
|
|
||||||
using tuple_arg_join = tuple_join_view<Char, T...>;
|
|
||||||
|
|
||||||
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
||||||
// support in tuple_join. It is disabled by default because of issues with
|
// support in tuple_join. It is disabled by default because of issues with
|
||||||
// the dynamic width and precision.
|
// the dynamic width and precision.
|
||||||
|
@ -582,7 +648,45 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_BEGIN
|
namespace detail {
|
||||||
|
// Check if T has an interface like a container adaptor (e.g. std::stack,
|
||||||
|
// std::queue, std::priority_queue).
|
||||||
|
template <typename T> class is_container_adaptor_like {
|
||||||
|
template <typename U> static auto check(U* p) -> typename U::container_type;
|
||||||
|
template <typename> static void check(...);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr const bool value =
|
||||||
|
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Container> struct all {
|
||||||
|
const Container& c;
|
||||||
|
auto begin() const -> typename Container::const_iterator { return c.begin(); }
|
||||||
|
auto end() const -> typename Container::const_iterator { return c.end(); }
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<
|
||||||
|
T, Char,
|
||||||
|
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
|
||||||
|
bool_constant<range_format_kind<T, Char>::value ==
|
||||||
|
range_format::disabled>>::value>>
|
||||||
|
: formatter<detail::all<typename T::container_type>, Char> {
|
||||||
|
using all = detail::all<typename T::container_type>;
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
|
struct getter : T {
|
||||||
|
static auto get(const T& t) -> all {
|
||||||
|
return {t.*(&getter::c)}; // Access c through the derived class.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return formatter<all>::format(getter::get(t), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\rst
|
\rst
|
||||||
|
@ -625,7 +729,7 @@ auto join(std::initializer_list<T> list, string_view sep)
|
||||||
return join(std::begin(list), std::end(list), sep);
|
return join(std::begin(list), std::end(list), sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_END
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_RANGES_H_
|
#endif // FMT_RANGES_H_
|
||||||
|
|
|
@ -8,10 +8,18 @@
|
||||||
#ifndef FMT_STD_H_
|
#ifndef FMT_STD_H_
|
||||||
#define FMT_STD_H_
|
#define FMT_STD_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <bitset>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <exception>
|
||||||
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <typeinfo>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "format.h"
|
||||||
#include "ostream.h"
|
#include "ostream.h"
|
||||||
|
|
||||||
#if FMT_HAS_INCLUDE(<version>)
|
#if FMT_HAS_INCLUDE(<version>)
|
||||||
|
@ -25,6 +33,30 @@
|
||||||
# if FMT_HAS_INCLUDE(<variant>)
|
# if FMT_HAS_INCLUDE(<variant>)
|
||||||
# include <variant>
|
# include <variant>
|
||||||
# endif
|
# endif
|
||||||
|
# if FMT_HAS_INCLUDE(<optional>)
|
||||||
|
# include <optional>
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// GCC 4 does not support FMT_HAS_INCLUDE.
|
||||||
|
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
|
||||||
|
# include <cxxabi.h>
|
||||||
|
// Android NDK with gabi++ library on some architectures does not implement
|
||||||
|
// abi::__cxa_demangle().
|
||||||
|
# ifndef __GABIXX_CXXABI_H__
|
||||||
|
# define FMT_HAS_ABI_CXA_DEMANGLE
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Check if typeid is available.
|
||||||
|
#ifndef FMT_USE_TYPEID
|
||||||
|
// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI.
|
||||||
|
# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || FMT_MSC_VERSION || \
|
||||||
|
defined(__INTEL_RTTI__) || defined(__RTTI)
|
||||||
|
# define FMT_USE_TYPEID 1
|
||||||
|
# else
|
||||||
|
# define FMT_USE_TYPEID 0
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cpp_lib_filesystem
|
#ifdef __cpp_lib_filesystem
|
||||||
|
@ -32,21 +64,32 @@ FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename Char> auto get_path_string(const std::filesystem::path& p) {
|
||||||
|
return p.string<Char>();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||||
const std::filesystem::path& p) {
|
const std::filesystem::path& p) {
|
||||||
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
template <>
|
template <>
|
||||||
inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted,
|
inline auto get_path_string<char>(const std::filesystem::path& p) {
|
||||||
const std::filesystem::path& p) {
|
return to_utf8<wchar_t>(p.native(), to_utf8_error_policy::replace);
|
||||||
auto s = p.u8string();
|
|
||||||
write_escaped_string<char>(
|
|
||||||
std::back_inserter(quoted),
|
|
||||||
string_view(reinterpret_cast<const char*>(s.c_str()), s.size()));
|
|
||||||
}
|
}
|
||||||
# endif
|
|
||||||
|
template <>
|
||||||
|
inline void write_escaped_path<char>(memory_buffer& quoted,
|
||||||
|
const std::filesystem::path& p) {
|
||||||
|
auto buf = basic_memory_buffer<wchar_t>();
|
||||||
|
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
|
||||||
|
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
|
||||||
|
FMT_ASSERT(valid, "invalid utf16");
|
||||||
|
}
|
||||||
|
# endif // _WIN32
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline void write_escaped_path<std::filesystem::path::value_type>(
|
inline void write_escaped_path<std::filesystem::path::value_type>(
|
||||||
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
|
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
|
||||||
|
@ -57,67 +100,118 @@ inline void write_escaped_path<std::filesystem::path::value_type>(
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
|
FMT_EXPORT
|
||||||
// For MSVC 2017 and earlier using the partial specialization
|
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||||
// would cause an ambiguity error, therefore we provide it only
|
private:
|
||||||
// conditionally.
|
format_specs<Char> specs_;
|
||||||
template <typename Char>
|
detail::arg_ref<Char> width_ref_;
|
||||||
struct formatter<std::filesystem::path, Char>
|
bool debug_ = false;
|
||||||
: formatter<basic_string_view<Char>> {
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||||
|
|
||||||
|
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||||
|
auto it = ctx.begin(), end = ctx.end();
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
it = detail::parse_align(it, end, specs_);
|
||||||
|
if (it == end) return it;
|
||||||
|
|
||||||
|
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
||||||
|
if (it != end && *it == '?') {
|
||||||
|
debug_ = true;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
|
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
|
||||||
typename FormatContext::iterator {
|
auto specs = specs_;
|
||||||
basic_memory_buffer<Char> quoted;
|
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
||||||
|
ctx);
|
||||||
|
if (!debug_) {
|
||||||
|
auto s = detail::get_path_string<Char>(p);
|
||||||
|
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
||||||
|
}
|
||||||
|
auto quoted = basic_memory_buffer<Char>();
|
||||||
detail::write_escaped_path(quoted, p);
|
detail::write_escaped_path(quoted, p);
|
||||||
return formatter<basic_string_view<Char>>::format(
|
return detail::write(ctx.out(),
|
||||||
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
|
basic_string_view<Char>(quoted.data(), quoted.size()),
|
||||||
|
specs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
FMT_EXPORT
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#ifdef __cpp_lib_variant
|
#ifdef __cpp_lib_optional
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <typename Char> struct formatter<std::monostate, Char> {
|
FMT_EXPORT
|
||||||
template <typename ParseContext>
|
template <typename T, typename Char>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
struct formatter<std::optional<T>, Char,
|
||||||
return ctx.begin();
|
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||||
|
private:
|
||||||
|
formatter<T, Char> underlying_;
|
||||||
|
static constexpr basic_string_view<Char> optional =
|
||||||
|
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
||||||
|
'('>{};
|
||||||
|
static constexpr basic_string_view<Char> none =
|
||||||
|
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
|
||||||
|
-> decltype(u.set_debug_format(set)) {
|
||||||
|
u.set_debug_format(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||||
|
maybe_set_debug_format(underlying_, true);
|
||||||
|
return underlying_.parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const std::monostate&, FormatContext& ctx) const
|
auto format(std::optional<T> const& opt, FormatContext& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
|
if (!opt) return detail::write<Char>(ctx.out(), none);
|
||||||
|
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
out = detail::write<Char>(out, "monostate");
|
out = detail::write<Char>(out, optional);
|
||||||
return out;
|
ctx.advance_to(out);
|
||||||
|
out = underlying_.format(*opt, ctx);
|
||||||
|
return detail::write(out, ')');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
#endif // __cpp_lib_optional
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_variant
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using variant_index_sequence =
|
using variant_index_sequence =
|
||||||
std::make_index_sequence<std::variant_size<T>::value>;
|
std::make_index_sequence<std::variant_size<T>::value>;
|
||||||
|
|
||||||
// variant_size and variant_alternative check.
|
template <typename> struct is_variant_like_ : std::false_type {};
|
||||||
template <typename T, typename U = void>
|
template <typename... Types>
|
||||||
struct is_variant_like_ : std::false_type {};
|
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||||
template <typename T>
|
|
||||||
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
|
|
||||||
: std::true_type {};
|
|
||||||
|
|
||||||
// formattable element check
|
// formattable element check.
|
||||||
template <typename T, typename C> class is_variant_formattable_ {
|
template <typename T, typename C> class is_variant_formattable_ {
|
||||||
template <std::size_t... I>
|
template <std::size_t... Is>
|
||||||
static std::conjunction<
|
static std::conjunction<
|
||||||
is_formattable<std::variant_alternative_t<I, T>, C>...>
|
is_formattable<std::variant_alternative_t<Is, T>, C>...>
|
||||||
check(std::index_sequence<I...>);
|
check(std::index_sequence<Is...>);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr const bool value =
|
static constexpr const bool value =
|
||||||
|
@ -145,6 +239,21 @@ template <typename T, typename C> struct is_variant_formattable {
|
||||||
detail::is_variant_formattable_<T, C>::value;
|
detail::is_variant_formattable_<T, C>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename Char> struct formatter<std::monostate, Char> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const std::monostate&, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return detail::write<Char>(ctx.out(), "monostate");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
template <typename Variant, typename Char>
|
template <typename Variant, typename Char>
|
||||||
struct formatter<
|
struct formatter<
|
||||||
Variant, Char,
|
Variant, Char,
|
||||||
|
@ -161,16 +270,196 @@ struct formatter<
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
|
|
||||||
out = detail::write<Char>(out, "variant(");
|
out = detail::write<Char>(out, "variant(");
|
||||||
std::visit(
|
FMT_TRY {
|
||||||
[&](const auto& v) {
|
std::visit(
|
||||||
out = detail::write_variant_alternative<Char>(out, v);
|
[&](const auto& v) {
|
||||||
},
|
out = detail::write_variant_alternative<Char>(out, v);
|
||||||
value);
|
},
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
FMT_CATCH(const std::bad_variant_access&) {
|
||||||
|
detail::write<Char>(out, "valueless by exception");
|
||||||
|
}
|
||||||
*out++ = ')';
|
*out++ = ')';
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
#endif // __cpp_lib_variant
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename Char> struct formatter<std::error_code, Char> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
auto out = ctx.out();
|
||||||
|
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
|
||||||
|
out = detail::write<Char>(out, Char(':'));
|
||||||
|
out = detail::write<Char>(out, ec.value());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<
|
||||||
|
T, Char,
|
||||||
|
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||||
|
private:
|
||||||
|
bool with_typename_ = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||||
|
-> decltype(ctx.begin()) {
|
||||||
|
auto it = ctx.begin();
|
||||||
|
auto end = ctx.end();
|
||||||
|
if (it == end || *it == '}') return it;
|
||||||
|
if (*it == 't') {
|
||||||
|
++it;
|
||||||
|
with_typename_ = FMT_USE_TYPEID != 0;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
auto format(const std::exception& ex,
|
||||||
|
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
|
||||||
|
format_specs<Char> spec;
|
||||||
|
auto out = ctx.out();
|
||||||
|
if (!with_typename_)
|
||||||
|
return detail::write_bytes(out, string_view(ex.what()), spec);
|
||||||
|
|
||||||
|
#if FMT_USE_TYPEID
|
||||||
|
const std::type_info& ti = typeid(ex);
|
||||||
|
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||||
|
int status = 0;
|
||||||
|
std::size_t size = 0;
|
||||||
|
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
|
||||||
|
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||||
|
|
||||||
|
string_view demangled_name_view;
|
||||||
|
if (demangled_name_ptr) {
|
||||||
|
demangled_name_view = demangled_name_ptr.get();
|
||||||
|
|
||||||
|
// Normalization of stdlib inline namespace names.
|
||||||
|
// libc++ inline namespaces.
|
||||||
|
// std::__1::* -> std::*
|
||||||
|
// std::__1::__fs::* -> std::*
|
||||||
|
// libstdc++ inline namespaces.
|
||||||
|
// std::__cxx11::* -> std::*
|
||||||
|
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||||
|
if (demangled_name_view.starts_with("std::")) {
|
||||||
|
char* begin = demangled_name_ptr.get();
|
||||||
|
char* to = begin + 5; // std::
|
||||||
|
for (char *from = to, *end = begin + demangled_name_view.size();
|
||||||
|
from < end;) {
|
||||||
|
// This is safe, because demangled_name is NUL-terminated.
|
||||||
|
if (from[0] == '_' && from[1] == '_') {
|
||||||
|
char* next = from + 1;
|
||||||
|
while (next < end && *next != ':') next++;
|
||||||
|
if (next[0] == ':' && next[1] == ':') {
|
||||||
|
from = next + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*to++ = *from++;
|
||||||
|
}
|
||||||
|
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
demangled_name_view = string_view(ti.name());
|
||||||
|
}
|
||||||
|
out = detail::write_bytes(out, demangled_name_view, spec);
|
||||||
|
# elif FMT_MSC_VERSION
|
||||||
|
string_view demangled_name_view(ti.name());
|
||||||
|
if (demangled_name_view.starts_with("class "))
|
||||||
|
demangled_name_view.remove_prefix(6);
|
||||||
|
else if (demangled_name_view.starts_with("struct "))
|
||||||
|
demangled_name_view.remove_prefix(7);
|
||||||
|
out = detail::write_bytes(out, demangled_name_view, spec);
|
||||||
|
# else
|
||||||
|
out = detail::write_bytes(out, string_view(ti.name()), spec);
|
||||||
|
# endif
|
||||||
|
*out++ = ':';
|
||||||
|
*out++ = ' ';
|
||||||
|
return detail::write_bytes(out, string_view(ex.what()), spec);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_flip : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T> struct is_bit_reference_like {
|
||||||
|
static constexpr const bool value =
|
||||||
|
std::is_convertible<T, bool>::value &&
|
||||||
|
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _LIBCPP_VERSION
|
||||||
|
|
||||||
|
// Workaround for libc++ incompatibility with C++ standard.
|
||||||
|
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||||
|
template <typename C>
|
||||||
|
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||||
|
static constexpr const bool value = true;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// We can't use std::vector<bool, Allocator>::reference and
|
||||||
|
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||||
|
// in partial specialization.
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename BitRef, typename Char>
|
||||||
|
struct formatter<BitRef, Char,
|
||||||
|
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||||
|
: formatter<bool, Char> {
|
||||||
|
template <typename FormatContext>
|
||||||
|
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return formatter<bool, Char>::format(v, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<std::atomic<T>, Char,
|
||||||
|
enable_if_t<is_formattable<T, Char>::value>>
|
||||||
|
: formatter<T, Char> {
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const std::atomic<T>& v, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return formatter<T, Char>::format(v.load(), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_atomic_flag_test
|
||||||
|
FMT_EXPORT
|
||||||
|
template <typename Char>
|
||||||
|
struct formatter<std::atomic_flag, Char>
|
||||||
|
: formatter<bool, Char> {
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const std::atomic_flag& v, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
return formatter<bool, Char>::format(v.test(), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // __cpp_lib_atomic_flag_test
|
||||||
|
|
||||||
|
FMT_END_NAMESPACE
|
||||||
#endif // FMT_STD_H_
|
#endif // FMT_STD_H_
|
||||||
|
|
|
@ -9,17 +9,35 @@
|
||||||
#define FMT_XCHAR_H_
|
#define FMT_XCHAR_H_
|
||||||
|
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
# include <locale>
|
||||||
|
#endif
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||||
}
|
|
||||||
|
|
||||||
FMT_MODULE_EXPORT_BEGIN
|
inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
|
||||||
|
loc_value value, const format_specs<wchar_t>& specs,
|
||||||
|
locale_ref loc) -> bool {
|
||||||
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
auto& numpunct =
|
||||||
|
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
|
||||||
|
auto separator = std::wstring();
|
||||||
|
auto grouping = numpunct.grouping();
|
||||||
|
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
|
||||||
|
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_BEGIN_EXPORT
|
||||||
|
|
||||||
using wstring_view = basic_string_view<wchar_t>;
|
using wstring_view = basic_string_view<wchar_t>;
|
||||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||||
|
@ -30,9 +48,13 @@ using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||||
// Workaround broken conversion on older gcc.
|
// Workaround broken conversion on older gcc.
|
||||||
template <typename... Args> using wformat_string = wstring_view;
|
template <typename... Args> using wformat_string = wstring_view;
|
||||||
|
inline auto runtime(wstring_view s) -> wstring_view { return s; }
|
||||||
#else
|
#else
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||||
|
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
||||||
|
return {{s}};
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <> struct is_char<wchar_t> : std::true_type {};
|
template <> struct is_char<wchar_t> : std::true_type {};
|
||||||
|
@ -40,9 +62,9 @@ template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||||
template <> struct is_char<char16_t> : std::true_type {};
|
template <> struct is_char<char16_t> : std::true_type {};
|
||||||
template <> struct is_char<char32_t> : std::true_type {};
|
template <> struct is_char<char32_t> : std::true_type {};
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... T>
|
||||||
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
constexpr format_arg_store<wformat_context, T...> make_wformat_args(
|
||||||
const Args&... args) {
|
const T&... args) {
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,26 +99,22 @@ template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||||
auto vformat(basic_string_view<Char> format_str,
|
auto vformat(basic_string_view<Char> format_str,
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
basic_memory_buffer<Char> buffer;
|
auto buf = basic_memory_buffer<Char>();
|
||||||
detail::vformat_to(buffer, format_str, args);
|
detail::vformat_to(buf, format_str, args);
|
||||||
return to_string(buffer);
|
return to_string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
|
|
||||||
template <typename... Args>
|
|
||||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
||||||
return vformat(fmt, fmt::make_wformat_args(args...));
|
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass char_t as a default template parameter instead of using
|
// Pass char_t as a default template parameter instead of using
|
||||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
template <typename S, typename... T, typename Char = char_t<S>,
|
||||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||||
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
!std::is_same<Char, wchar_t>::value)>
|
||||||
|
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
|
||||||
return vformat(detail::to_string_view(format_str),
|
return vformat(detail::to_string_view(format_str),
|
||||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
@ -111,11 +129,10 @@ inline auto vformat(
|
||||||
return detail::vformat(loc, detail::to_string_view(format_str), args);
|
return detail::vformat(loc, detail::to_string_view(format_str), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Locale, typename S, typename... Args,
|
template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
|
||||||
typename Char = char_t<S>,
|
|
||||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||||
detail::is_exotic_char<Char>::value)>
|
detail::is_exotic_char<Char>::value)>
|
||||||
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
inline auto format(const Locale& loc, const S& format_str, T&&... args)
|
||||||
-> std::basic_string<Char> {
|
-> std::basic_string<Char> {
|
||||||
return detail::vformat(loc, detail::to_string_view(format_str),
|
return detail::vformat(loc, detail::to_string_view(format_str),
|
||||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||||
|
@ -129,14 +146,14 @@ auto vformat_to(OutputIt out, const S& format_str,
|
||||||
-> OutputIt {
|
-> OutputIt {
|
||||||
auto&& buf = detail::get_buffer<Char>(out);
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
detail::vformat_to(buf, detail::to_string_view(format_str), args);
|
detail::vformat_to(buf, detail::to_string_view(format_str), args);
|
||||||
return detail::get_iterator(buf);
|
return detail::get_iterator(buf, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
template <typename OutputIt, typename S, typename... T,
|
||||||
typename Char = char_t<S>,
|
typename Char = char_t<S>,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
detail::is_exotic_char<Char>::value)>
|
detail::is_exotic_char<Char>::value)>
|
||||||
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
|
||||||
return vformat_to(out, detail::to_string_view(fmt),
|
return vformat_to(out, detail::to_string_view(fmt),
|
||||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
@ -152,18 +169,18 @@ inline auto vformat_to(
|
||||||
auto&& buf = detail::get_buffer<Char>(out);
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
vformat_to(buf, detail::to_string_view(format_str), args,
|
vformat_to(buf, detail::to_string_view(format_str), args,
|
||||||
detail::locale_ref(loc));
|
detail::locale_ref(loc));
|
||||||
return detail::get_iterator(buf);
|
return detail::get_iterator(buf, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
typename OutputIt, typename Locale, typename S, typename... Args,
|
typename OutputIt, typename Locale, typename S, typename... T,
|
||||||
typename Char = char_t<S>,
|
typename Char = char_t<S>,
|
||||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||||
Args&&... args) ->
|
T&&... args) ->
|
||||||
typename std::enable_if<enable, OutputIt>::type {
|
typename std::enable_if<enable, OutputIt>::type {
|
||||||
return vformat_to(out, loc, to_string_view(format_str),
|
return vformat_to(out, loc, detail::to_string_view(format_str),
|
||||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,36 +191,36 @@ inline auto vformat_to_n(
|
||||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
-> format_to_n_result<OutputIt> {
|
-> format_to_n_result<OutputIt> {
|
||||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
using traits = detail::fixed_buffer_traits;
|
||||||
n);
|
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
||||||
detail::vformat_to(buf, format_str, args);
|
detail::vformat_to(buf, format_str, args);
|
||||||
return {buf.out(), buf.count()};
|
return {buf.out(), buf.count()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
template <typename OutputIt, typename S, typename... T,
|
||||||
typename Char = char_t<S>,
|
typename Char = char_t<S>,
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
detail::is_exotic_char<Char>::value)>
|
detail::is_exotic_char<Char>::value)>
|
||||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||||
const Args&... args) -> format_to_n_result<OutputIt> {
|
-> format_to_n_result<OutputIt> {
|
||||||
return vformat_to_n(out, n, detail::to_string_view(fmt),
|
return vformat_to_n(out, n, detail::to_string_view(fmt),
|
||||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
template <typename S, typename... T, typename Char = char_t<S>,
|
||||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||||
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||||
detail::counting_buffer<Char> buf;
|
auto buf = detail::counting_buffer<Char>();
|
||||||
detail::vformat_to(buf, detail::to_string_view(fmt),
|
detail::vformat_to(buf, detail::to_string_view(fmt),
|
||||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||||
return buf.count();
|
return buf.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||||
wmemory_buffer buffer;
|
auto buf = wmemory_buffer();
|
||||||
detail::vformat_to(buffer, fmt, args);
|
detail::vformat_to(buf, fmt, args);
|
||||||
buffer.push_back(L'\0');
|
buf.push_back(L'\0');
|
||||||
if (std::fputws(buffer.data(), f) == -1)
|
if (std::fputws(buf.data(), f) == -1)
|
||||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,13 +237,22 @@ template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||||
|
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
|
||||||
|
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||||
*/
|
*/
|
||||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||||
return format(FMT_STRING(L"{}"), value);
|
return format(FMT_STRING(L"{}"), value);
|
||||||
}
|
}
|
||||||
FMT_MODULE_EXPORT_END
|
FMT_END_EXPORT
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_XCHAR_H_
|
#endif // FMT_XCHAR_H_
|
||||||
|
|
69
src/fmt.cc
69
src/fmt.cc
|
@ -1,49 +1,44 @@
|
||||||
module;
|
module;
|
||||||
#ifndef __cpp_modules
|
|
||||||
# error Module not supported.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// put all implementation-provided headers into the global module fragment
|
|
||||||
// to prevent attachment to this module
|
|
||||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
|
||||||
# define _CRT_SECURE_NO_WARNINGS
|
|
||||||
#endif
|
|
||||||
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
|
|
||||||
# define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
// Put all implementation-provided headers into the global module fragment
|
||||||
|
// to prevent attachment to this module.
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <clocale>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <cwchar>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <sstream>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <typeinfo>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <version>
|
||||||
|
|
||||||
#if _MSC_VER
|
#if __has_include(<cxxabi.h>)
|
||||||
|
# include <cxxabi.h>
|
||||||
|
#endif
|
||||||
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
# include <intrin.h>
|
# include <intrin.h>
|
||||||
#endif
|
#endif
|
||||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||||
|
@ -65,22 +60,33 @@ module;
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
# if defined(__GLIBCXX__)
|
||||||
|
# include <ext/stdio_filebuf.h>
|
||||||
|
# include <ext/stdio_sync_filebuf.h>
|
||||||
|
# elif defined(_LIBCPP_VERSION)
|
||||||
|
# include <__std_stream>
|
||||||
|
# endif
|
||||||
|
# define WIN32_LEAN_AND_MEAN
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
export module fmt;
|
export module fmt;
|
||||||
|
|
||||||
#define FMT_MODULE_EXPORT export
|
#define FMT_EXPORT export
|
||||||
#define FMT_MODULE_EXPORT_BEGIN export {
|
#define FMT_BEGIN_EXPORT export {
|
||||||
#define FMT_MODULE_EXPORT_END }
|
#define FMT_END_EXPORT }
|
||||||
#define FMT_BEGIN_DETAIL_NAMESPACE \
|
|
||||||
} \
|
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
|
||||||
namespace detail {
|
// - all declarations are detached from module 'fmt'
|
||||||
#define FMT_END_DETAIL_NAMESPACE \
|
// - the module behaves like a traditional static library, too
|
||||||
} \
|
// - all library symbols are mangled traditionally
|
||||||
export {
|
// - you can mix TUs with either importing or #including the {fmt} API
|
||||||
// all library-provided declarations and definitions
|
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||||
// must be in the module purview to be exported
|
extern "C++" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// All library-provided declarations and definitions must be in the module
|
||||||
|
// purview to be exported.
|
||||||
#include "fmt/args.h"
|
#include "fmt/args.h"
|
||||||
#include "fmt/chrono.h"
|
#include "fmt/chrono.h"
|
||||||
#include "fmt/color.h"
|
#include "fmt/color.h"
|
||||||
|
@ -88,8 +94,13 @@ export module fmt;
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "fmt/os.h"
|
#include "fmt/os.h"
|
||||||
#include "fmt/printf.h"
|
#include "fmt/printf.h"
|
||||||
|
#include "fmt/std.h"
|
||||||
#include "fmt/xchar.h"
|
#include "fmt/xchar.h"
|
||||||
|
|
||||||
|
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// gcc doesn't yet implement private module fragments
|
// gcc doesn't yet implement private module fragments
|
||||||
#if !FMT_GCC_VERSION
|
#if !FMT_GCC_VERSION
|
||||||
module : private;
|
module : private;
|
||||||
|
|
|
@ -28,12 +28,8 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char;
|
||||||
|
|
||||||
template FMT_API void buffer<char>::append(const char*, const char*);
|
template FMT_API void buffer<char>::append(const char*, const char*);
|
||||||
|
|
||||||
// DEPRECATED!
|
|
||||||
// There is no correspondent extern template in format.h because of
|
|
||||||
// incompatibility between clang and gcc (#2377).
|
|
||||||
template FMT_API void vformat_to(buffer<char>&, string_view,
|
template FMT_API void vformat_to(buffer<char>&, string_view,
|
||||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>,
|
typename vformat_args<>::type, locale_ref);
|
||||||
locale_ref);
|
|
||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
// Explicit instantiations for wchar_t.
|
||||||
|
|
||||||
|
|
165
src/os.cc
165
src/os.cc
|
@ -18,6 +18,10 @@
|
||||||
# include <sys/stat.h>
|
# include <sys/stat.h>
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
|
|
||||||
|
# ifdef _WRS_KERNEL // VxWorks7 kernel
|
||||||
|
# include <ioLib.h> // getpagesize
|
||||||
|
# endif
|
||||||
|
|
||||||
# ifndef _WIN32
|
# ifndef _WIN32
|
||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
# else
|
# else
|
||||||
|
@ -72,34 +76,6 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
|
|
||||||
if (int error_code = convert(s)) {
|
|
||||||
FMT_THROW(windows_error(error_code,
|
|
||||||
"cannot convert string from UTF-16 to UTF-8"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
|
|
||||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
|
||||||
int s_size = static_cast<int>(s.size());
|
|
||||||
if (s_size == 0) {
|
|
||||||
// WideCharToMultiByte does not support zero length, handle separately.
|
|
||||||
buffer_.resize(1);
|
|
||||||
buffer_[0] = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
|
|
||||||
nullptr, nullptr);
|
|
||||||
if (length == 0) return GetLastError();
|
|
||||||
buffer_.resize(length + 1);
|
|
||||||
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
|
|
||||||
length, nullptr, nullptr);
|
|
||||||
if (length == 0) return GetLastError();
|
|
||||||
buffer_[length] = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
class system_message {
|
class system_message {
|
||||||
|
@ -138,10 +114,10 @@ class utf8_system_category final : public std::error_category {
|
||||||
public:
|
public:
|
||||||
const char* name() const noexcept override { return "system"; }
|
const char* name() const noexcept override { return "system"; }
|
||||||
std::string message(int error_code) const override {
|
std::string message(int error_code) const override {
|
||||||
system_message msg(error_code);
|
auto&& msg = system_message(error_code);
|
||||||
if (msg) {
|
if (msg) {
|
||||||
utf16_to_utf8 utf8_message;
|
auto utf8_message = to_utf8<wchar_t>();
|
||||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
if (utf8_message.convert(msg)) {
|
||||||
return utf8_message.str();
|
return utf8_message.str();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,11 +141,12 @@ std::system_error vwindows_error(int err_code, string_view format_str,
|
||||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||||
const char* message) noexcept {
|
const char* message) noexcept {
|
||||||
FMT_TRY {
|
FMT_TRY {
|
||||||
system_message msg(error_code);
|
auto&& msg = system_message(error_code);
|
||||||
if (msg) {
|
if (msg) {
|
||||||
utf16_to_utf8 utf8_message;
|
auto utf8_message = to_utf8<wchar_t>();
|
||||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
if (utf8_message.convert(msg)) {
|
||||||
fmt::format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
|
fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
|
||||||
|
string_view(utf8_message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,37 +169,47 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||||
nullptr);
|
nullptr);
|
||||||
if (!file_)
|
if (!file_)
|
||||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
|
||||||
|
filename.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffered_file::close() {
|
void buffered_file::close() {
|
||||||
if (!file_) return;
|
if (!file_) return;
|
||||||
int result = FMT_SYSTEM(fclose(file_));
|
int result = FMT_SYSTEM(fclose(file_));
|
||||||
file_ = nullptr;
|
file_ = nullptr;
|
||||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
if (result != 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||||
}
|
}
|
||||||
|
|
||||||
int buffered_file::descriptor() const {
|
int buffered_file::descriptor() const {
|
||||||
|
#ifdef fileno // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL.
|
||||||
|
int fd = fileno(file_);
|
||||||
|
#else
|
||||||
int fd = FMT_POSIX_CALL(fileno(file_));
|
int fd = FMT_POSIX_CALL(fileno(file_));
|
||||||
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
#endif
|
||||||
|
if (fd == -1)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
file::file(cstring_view path, int oflag) {
|
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
using mode_t = int;
|
using mode_t = int;
|
||||||
# endif
|
# endif
|
||||||
constexpr mode_t mode =
|
constexpr mode_t default_open_mode =
|
||||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||||
|
|
||||||
|
file::file(cstring_view path, int oflag) {
|
||||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||||
fd_ = -1;
|
fd_ = -1;
|
||||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
|
||||||
|
*this = file::open_windows_file(converted.c_str(), oflag);
|
||||||
# else
|
# else
|
||||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
|
||||||
# endif
|
|
||||||
if (fd_ == -1)
|
if (fd_ == -1)
|
||||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
FMT_THROW(
|
||||||
|
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
file::~file() noexcept {
|
file::~file() noexcept {
|
||||||
|
@ -238,7 +225,8 @@ void file::close() {
|
||||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||||
int result = FMT_POSIX_CALL(close(fd_));
|
int result = FMT_POSIX_CALL(close(fd_));
|
||||||
fd_ = -1;
|
fd_ = -1;
|
||||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
if (result != 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||||
}
|
}
|
||||||
|
|
||||||
long long file::size() const {
|
long long file::size() const {
|
||||||
|
@ -260,7 +248,7 @@ long long file::size() const {
|
||||||
using Stat = struct stat;
|
using Stat = struct stat;
|
||||||
Stat file_stat = Stat();
|
Stat file_stat = Stat();
|
||||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
|
||||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||||
"return type of file::size is not large enough");
|
"return type of file::size is not large enough");
|
||||||
return file_stat.st_size;
|
return file_stat.st_size;
|
||||||
|
@ -270,14 +258,16 @@ long long file::size() const {
|
||||||
std::size_t file::read(void* buffer, std::size_t count) {
|
std::size_t file::read(void* buffer, std::size_t count) {
|
||||||
rwresult result = 0;
|
rwresult result = 0;
|
||||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
if (result < 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
|
||||||
return detail::to_unsigned(result);
|
return detail::to_unsigned(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||||
rwresult result = 0;
|
rwresult result = 0;
|
||||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
if (result < 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||||
return detail::to_unsigned(result);
|
return detail::to_unsigned(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +276,8 @@ file file::dup(int fd) {
|
||||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||||
if (new_fd == -1)
|
if (new_fd == -1)
|
||||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
FMT_THROW(system_error(
|
||||||
|
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
|
||||||
return file(new_fd);
|
return file(new_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,8 +285,9 @@ void file::dup2(int fd) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||||
if (result == -1) {
|
if (result == -1) {
|
||||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
|
FMT_THROW(system_error(
|
||||||
fd_, fd));
|
errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
|
||||||
|
fd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,7 +312,8 @@ void file::pipe(file& read_end, file& write_end) {
|
||||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||||
int result = FMT_POSIX_CALL(pipe(fds));
|
int result = FMT_POSIX_CALL(pipe(fds));
|
||||||
# endif
|
# endif
|
||||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
if (result != 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
|
||||||
// The following assignments don't throw because read_fd and write_fd
|
// The following assignments don't throw because read_fd and write_fd
|
||||||
// are closed.
|
// are closed.
|
||||||
read_end = file(fds[0]);
|
read_end = file(fds[0]);
|
||||||
|
@ -334,28 +327,72 @@ buffered_file file::fdopen(const char* mode) {
|
||||||
# else
|
# else
|
||||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||||
# endif
|
# endif
|
||||||
if (!f)
|
if (!f) {
|
||||||
FMT_THROW(
|
FMT_THROW(system_error(
|
||||||
system_error(errno, "cannot associate stream with file descriptor"));
|
errno, FMT_STRING("cannot associate stream with file descriptor")));
|
||||||
|
}
|
||||||
buffered_file bf(f);
|
buffered_file bf(f);
|
||||||
fd_ = -1;
|
fd_ = -1;
|
||||||
return bf;
|
return bf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||||
|
file file::open_windows_file(wcstring_view path, int oflag) {
|
||||||
|
int fd = -1;
|
||||||
|
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
|
||||||
|
if (fd == -1) {
|
||||||
|
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
|
||||||
|
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
|
||||||
|
}
|
||||||
|
return file(fd);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# if !defined(__MSDOS__)
|
||||||
long getpagesize() {
|
long getpagesize() {
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
SYSTEM_INFO si;
|
SYSTEM_INFO si;
|
||||||
GetSystemInfo(&si);
|
GetSystemInfo(&si);
|
||||||
return si.dwPageSize;
|
return si.dwPageSize;
|
||||||
# else
|
# else
|
||||||
|
# ifdef _WRS_KERNEL
|
||||||
|
long size = FMT_POSIX_CALL(getpagesize());
|
||||||
|
# else
|
||||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
# endif
|
||||||
return size;
|
|
||||||
# endif
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_API void ostream::grow(size_t) {
|
if (size < 0)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
|
||||||
|
return size;
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
void file_buffer::grow(size_t) {
|
||||||
if (this->size() == this->capacity()) flush();
|
if (this->size() == this->capacity()) flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file_buffer::file_buffer(cstring_view path,
|
||||||
|
const detail::ostream_params& params)
|
||||||
|
: file_(path, params.oflag) {
|
||||||
|
set(new char[params.buffer_size], params.buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_buffer::file_buffer(file_buffer&& other)
|
||||||
|
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||||
|
file_(std::move(other.file_)) {
|
||||||
|
other.clear();
|
||||||
|
other.set(nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_buffer::~file_buffer() {
|
||||||
|
flush();
|
||||||
|
delete[] data();
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
ostream::~ostream() = default;
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
6
support/Vagrantfile
vendored
6
support/Vagrantfile
vendored
|
@ -13,8 +13,8 @@ Vagrant.configure("2") do |config|
|
||||||
config.vm.provision "shell", inline: <<-SHELL
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y g++ make wget git
|
apt-get install -y g++ make wget git
|
||||||
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz
|
wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz
|
||||||
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
|
tar xzf cmake-3.26.0-Linux-x86_64.tar.gz
|
||||||
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
|
ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
|
||||||
SHELL
|
SHELL
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# Build the project on AppVeyor.
|
|
||||||
|
|
||||||
import os
|
|
||||||
from subprocess import check_call
|
|
||||||
|
|
||||||
build = os.environ['BUILD']
|
|
||||||
config = os.environ['CONFIGURATION']
|
|
||||||
platform = os.environ['PLATFORM']
|
|
||||||
path = os.environ['PATH']
|
|
||||||
image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
|
|
||||||
jobid = os.environ['APPVEYOR_JOB_ID']
|
|
||||||
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
|
|
||||||
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\15.0\Bin;' + path
|
|
||||||
if image == 'Visual Studio 2019':
|
|
||||||
generator = 'Visual Studio 16 2019'
|
|
||||||
if platform == 'x64':
|
|
||||||
cmake_command.extend(['-A', 'x64'])
|
|
||||||
else:
|
|
||||||
if image == 'Visual Studio 2015':
|
|
||||||
generator = 'Visual Studio 14 2015'
|
|
||||||
elif image == 'Visual Studio 2017':
|
|
||||||
generator = 'Visual Studio 15 2017'
|
|
||||||
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)
|
|
||||||
check_call(test_command)
|
|
|
@ -1,31 +0,0 @@
|
||||||
configuration:
|
|
||||||
- Debug
|
|
||||||
- Release
|
|
||||||
|
|
||||||
clone_depth: 1
|
|
||||||
|
|
||||||
image:
|
|
||||||
- Visual Studio 2015
|
|
||||||
|
|
||||||
platform:
|
|
||||||
- x64
|
|
||||||
|
|
||||||
environment:
|
|
||||||
CTEST_OUTPUT_ON_FAILURE: 1
|
|
||||||
MSVC_DEFAULT_OPTIONS: ON
|
|
||||||
BUILD: msvc
|
|
||||||
|
|
||||||
before_build:
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- python ../support/appveyor-build.py
|
|
||||||
|
|
||||||
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'))
|
|
|
@ -1 +0,0 @@
|
||||||
build --symlink_prefix=/ # Out of source build
|
|
|
@ -1 +1 @@
|
||||||
5.1.1
|
6.1.2
|
||||||
|
|
|
@ -11,8 +11,8 @@ cc_library(
|
||||||
"include/fmt/color.h",
|
"include/fmt/color.h",
|
||||||
"include/fmt/compile.h",
|
"include/fmt/compile.h",
|
||||||
"include/fmt/core.h",
|
"include/fmt/core.h",
|
||||||
"include/fmt/format-inl.h",
|
|
||||||
"include/fmt/format.h",
|
"include/fmt/format.h",
|
||||||
|
"include/fmt/format-inl.h",
|
||||||
"include/fmt/os.h",
|
"include/fmt/os.h",
|
||||||
"include/fmt/ostream.h",
|
"include/fmt/ostream.h",
|
||||||
"include/fmt/printf.h",
|
"include/fmt/printf.h",
|
||||||
|
@ -21,7 +21,7 @@ cc_library(
|
||||||
"include/fmt/xchar.h",
|
"include/fmt/xchar.h",
|
||||||
],
|
],
|
||||||
includes = [
|
includes = [
|
||||||
"include",
|
"include",
|
||||||
],
|
],
|
||||||
strip_include_prefix = "include",
|
strip_include_prefix = "include",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Bazel support
|
# Bazel support
|
||||||
|
|
||||||
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
|
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
|
||||||
|
|
||||||
## Using {fmt} as a dependency
|
## Using {fmt} as a dependency
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ git_repository(
|
||||||
branch = "master",
|
branch = "master",
|
||||||
remote = "https://github.com/fmtlib/fmt",
|
remote = "https://github.com/fmtlib/fmt",
|
||||||
patch_cmds = [
|
patch_cmds = [
|
||||||
"mv support/bazel/.bazelrc .bazelrc",
|
|
||||||
"mv support/bazel/.bazelversion .bazelversion",
|
"mv support/bazel/.bazelversion .bazelversion",
|
||||||
"mv support/bazel/BUILD.bazel BUILD.bazel",
|
"mv support/bazel/BUILD.bazel BUILD.bazel",
|
||||||
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
|
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
|
||||||
|
@ -47,7 +46,6 @@ git_repository(
|
||||||
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
|
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
|
||||||
# Even if MSYS2 is installed the Windows related patch commands can still be used.
|
# Even if MSYS2 is installed the Windows related patch commands can still be used.
|
||||||
patch_cmds_win = [
|
patch_cmds_win = [
|
||||||
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
|
|
||||||
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
|
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
|
||||||
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
|
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
|
||||||
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
|
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
|
||||||
|
@ -55,7 +53,7 @@ git_repository(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
|
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
|
||||||
|
|
||||||
*BUILD.bazel*:
|
*BUILD.bazel*:
|
||||||
|
|
||||||
|
@ -71,3 +69,6 @@ The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}.
|
||||||
|
|
||||||
To execute the binary you can run `bazel run //:Demo`.
|
To execute the binary you can run `bazel run //:Demo`.
|
||||||
|
|
||||||
|
# Using Bzlmod
|
||||||
|
|
||||||
|
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) also provides support for {fmt}.
|
||||||
|
|
|
@ -11,7 +11,7 @@ buildscript {
|
||||||
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
||||||
//
|
//
|
||||||
// Notice that 4.0.0 here is the version of [Android Gradle Plugin]
|
// Notice that 4.0.0 here is the version of [Android Gradle Plugin]
|
||||||
// Accroding to URL above you will need Gradle 6.1 or higher
|
// According to URL above you will need Gradle 6.1 or higher
|
||||||
//
|
//
|
||||||
classpath "com.android.tools.build:gradle:4.1.1"
|
classpath "com.android.tools.build:gradle:4.1.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
# C++14 feature support detection
|
|
||||||
|
|
||||||
include(CheckCXXCompilerFlag)
|
|
||||||
function (fmt_check_cxx_compiler_flag flag result)
|
|
||||||
if (NOT MSVC)
|
|
||||||
check_cxx_compiler_flag("${flag}" ${result})
|
|
||||||
endif ()
|
|
||||||
endfunction ()
|
|
||||||
|
|
||||||
if (NOT CMAKE_CXX_STANDARD)
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
endif()
|
|
||||||
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
|
|
||||||
|
|
||||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
|
|
||||||
|
|
||||||
if (has_std_20_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++20)
|
|
||||||
elseif (has_std_2a_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++2a)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
elseif (CMAKE_CXX_STANDARD EQUAL 17)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
|
|
||||||
|
|
||||||
if (has_std_17_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++17)
|
|
||||||
elseif (has_std_1z_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++1z)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
elseif (CMAKE_CXX_STANDARD EQUAL 14)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
|
|
||||||
|
|
||||||
if (has_std_14_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++14)
|
|
||||||
elseif (has_std_1y_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++1y)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
elseif (CMAKE_CXX_STANDARD EQUAL 11)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
|
|
||||||
fmt_check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
|
|
||||||
|
|
||||||
if (has_std_11_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++11)
|
|
||||||
elseif (has_std_0x_flag)
|
|
||||||
set(CXX_STANDARD_FLAG -std=c++0x)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
|
@ -183,6 +183,12 @@ def update_site(env):
|
||||||
with rewrite(index) as b:
|
with rewrite(index) as b:
|
||||||
b.data = b.data.replace(
|
b.data = b.data.replace(
|
||||||
'doc/latest/index.html#format-string-syntax', 'syntax.html')
|
'doc/latest/index.html#format-string-syntax', 'syntax.html')
|
||||||
|
# Fix issues in syntax.rst.
|
||||||
|
index = os.path.join(target_doc_dir, 'syntax.rst')
|
||||||
|
with rewrite(index) as b:
|
||||||
|
b.data = b.data.replace(
|
||||||
|
'..productionlist:: sf\n', '.. productionlist:: sf\n ')
|
||||||
|
b.data = b.data.replace('Examples:\n', 'Examples::\n')
|
||||||
# Build the docs.
|
# Build the docs.
|
||||||
html_dir = os.path.join(env.build_dir, 'html')
|
html_dir = os.path.join(env.build_dir, 'html')
|
||||||
if os.path.exists(html_dir):
|
if os.path.exists(html_dir):
|
||||||
|
|
|
@ -138,6 +138,13 @@ class Translator(nodes.NodeVisitor):
|
||||||
def depart_table(self, node):
|
def depart_table(self, node):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def visit_system_message(self, node):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depart_system_message(self, node):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MDWriter(writers.Writer):
|
class MDWriter(writers.Writer):
|
||||||
"""GitHub-flavored markdown writer"""
|
"""GitHub-flavored markdown writer"""
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
add_subdirectory(gtest)
|
add_subdirectory(gtest)
|
||||||
|
|
||||||
|
include(CheckSymbolExists)
|
||||||
|
|
||||||
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
||||||
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
||||||
target_include_directories(test-main PUBLIC
|
target_include_directories(test-main PUBLIC
|
||||||
|
@ -8,9 +10,6 @@ target_link_libraries(test-main gtest fmt)
|
||||||
|
|
||||||
function(add_fmt_executable name)
|
function(add_fmt_executable name)
|
||||||
add_executable(${name} ${ARGN})
|
add_executable(${name} ${ARGN})
|
||||||
if (MINGW)
|
|
||||||
target_link_libraries(${name} -static-libgcc -static-libstdc++)
|
|
||||||
endif ()
|
|
||||||
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
|
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
|
||||||
# Bogus -Wstringop-overflow warning
|
# Bogus -Wstringop-overflow warning
|
||||||
|
@ -19,6 +18,8 @@ function(add_fmt_executable name)
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
|
||||||
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
|
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
|
||||||
|
target_compile_options(${name} PRIVATE -Wno-stringop-overflow)
|
||||||
|
# The linker flag is needed for LTO.
|
||||||
target_link_libraries(${name} -Wno-stringop-overflow)
|
target_link_libraries(${name} -Wno-stringop-overflow)
|
||||||
endif ()
|
endif ()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
@ -36,7 +37,7 @@ function(add_fmt_test name)
|
||||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
|
||||||
endif ()
|
endif ()
|
||||||
elseif (ADD_FMT_TEST_MODULE)
|
elseif (ADD_FMT_TEST_MODULE)
|
||||||
set(libs gtest test-module)
|
set(libs test-main test-module)
|
||||||
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
|
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
|
||||||
else ()
|
else ()
|
||||||
set(libs test-main fmt)
|
set(libs test-main fmt)
|
||||||
|
@ -54,6 +55,10 @@ function(add_fmt_test name)
|
||||||
add_test(NAME ${name} COMMAND ${name})
|
add_test(NAME ${name} COMMAND ${name})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
if (FMT_MODULE)
|
||||||
|
return ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
add_fmt_test(args-test)
|
add_fmt_test(args-test)
|
||||||
add_fmt_test(assert-test)
|
add_fmt_test(assert-test)
|
||||||
add_fmt_test(chrono-test)
|
add_fmt_test(chrono-test)
|
||||||
|
@ -76,7 +81,13 @@ if (MSVC)
|
||||||
endif()
|
endif()
|
||||||
add_fmt_test(printf-test)
|
add_fmt_test(printf-test)
|
||||||
add_fmt_test(ranges-test ranges-odr-test.cc)
|
add_fmt_test(ranges-test ranges-odr-test.cc)
|
||||||
|
|
||||||
add_fmt_test(scan-test)
|
add_fmt_test(scan-test)
|
||||||
|
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
|
||||||
|
if (HAVE_STRPTIME)
|
||||||
|
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
|
||||||
|
endif ()
|
||||||
|
|
||||||
add_fmt_test(std-test)
|
add_fmt_test(std-test)
|
||||||
try_compile(compile_result_unused
|
try_compile(compile_result_unused
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
@ -95,13 +106,13 @@ add_fmt_test(enforce-checks-test)
|
||||||
target_compile_definitions(enforce-checks-test PRIVATE
|
target_compile_definitions(enforce-checks-test PRIVATE
|
||||||
-DFMT_ENFORCE_COMPILE_STRING)
|
-DFMT_ENFORCE_COMPILE_STRING)
|
||||||
|
|
||||||
if (FMT_CAN_MODULE)
|
if (FMT_MODULE)
|
||||||
# The tests need {fmt} to be compiled as traditional library
|
# The tests need {fmt} to be compiled as traditional library
|
||||||
# because of visibility of implementation details.
|
# because of visibility of implementation details.
|
||||||
# If module support is present the module tests require a
|
# If module support is present the module tests require a
|
||||||
# test-only module to be built from {fmt}
|
# test-only module to be built from {fmt}
|
||||||
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
|
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
|
||||||
target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES})
|
target_compile_features(test-module PUBLIC cxx_std_11)
|
||||||
target_include_directories(test-module PUBLIC
|
target_include_directories(test-module PUBLIC
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||||
enable_module(test-module)
|
enable_module(test-module)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.1...3.18)
|
cmake_minimum_required(VERSION 3.8...3.25)
|
||||||
|
|
||||||
project(fmt-test CXX)
|
project(fmt-test CXX)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ template <> struct formatter<custom_type> {
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
return format_to(ctx.out(), "cust={}", p.i);
|
return fmt::format_to(ctx.out(), "cust={}", p.i);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "fmt/chrono.h"
|
#include "fmt/chrono.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -14,9 +15,15 @@
|
||||||
#include "util.h" // get_locale
|
#include "util.h" // get_locale
|
||||||
|
|
||||||
using fmt::runtime;
|
using fmt::runtime;
|
||||||
|
|
||||||
using testing::Contains;
|
using testing::Contains;
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||||
|
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||||
|
# define FMT_HAS_C99_STRFTIME 0
|
||||||
|
#else
|
||||||
|
# define FMT_HAS_C99_STRFTIME 1
|
||||||
|
#endif
|
||||||
|
|
||||||
auto make_tm() -> std::tm {
|
auto make_tm() -> std::tm {
|
||||||
auto time = std::tm();
|
auto time = std::tm();
|
||||||
time.tm_mday = 1;
|
time.tm_mday = 1;
|
||||||
|
@ -121,6 +128,13 @@ TEST(chrono_test, format_tm) {
|
||||||
make_tm(2000, 1, 2, 12, 14, 16), // W52
|
make_tm(2000, 1, 2, 12, 14, 16), // W52
|
||||||
make_tm(2000, 1, 3, 12, 14, 16) // W1
|
make_tm(2000, 1, 3, 12, 14, 16) // W1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if !FMT_HAS_C99_STRFTIME
|
||||||
|
GTEST_SKIP() << "Skip the rest of this test because it relies on strftime() "
|
||||||
|
"conforming to C99, but on this platform, MINGW + MSVCRT, "
|
||||||
|
"the function conforms only to C89.";
|
||||||
|
#endif
|
||||||
|
|
||||||
const std::string iso_week_spec = "%Y-%m-%d: %G %g %V";
|
const std::string iso_week_spec = "%Y-%m-%d: %G %g %V";
|
||||||
for (auto ctm : tm_list) {
|
for (auto ctm : tm_list) {
|
||||||
// Calculate tm_yday, tm_wday, etc.
|
// Calculate tm_yday, tm_wday, etc.
|
||||||
|
@ -227,32 +241,135 @@ auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
|
||||||
lhs.tm_isdst == rhs.tm_isdst;
|
lhs.tm_isdst == rhs.tm_isdst;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, localtime) {
|
|
||||||
auto t = std::time(nullptr);
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(chrono_test, gmtime) {
|
TEST(chrono_test, gmtime) {
|
||||||
auto t = std::time(nullptr);
|
auto t = std::time(nullptr);
|
||||||
auto tm = *std::gmtime(&t);
|
auto tm = *std::gmtime(&t);
|
||||||
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string {
|
template <typename TimePoint>
|
||||||
|
auto strftime_full_utc(TimePoint tp) -> std::string {
|
||||||
auto t = std::chrono::system_clock::to_time_t(tp);
|
auto t = std::chrono::system_clock::to_time_t(tp);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, system_clock_time_point) {
|
||||||
|
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||||
|
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
|
||||||
|
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
|
||||||
|
using time_point =
|
||||||
|
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
|
||||||
|
auto t2 = time_point(std::chrono::seconds(42));
|
||||||
|
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||||
|
|
||||||
|
std::vector<std::string> spec_list = {
|
||||||
|
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||||
|
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
||||||
|
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
||||||
|
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||||
|
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||||
|
"%EX", "%D", "%F", "%R", "%T", "%p"};
|
||||||
|
#ifndef _WIN32
|
||||||
|
// Disabled on Windows because these formats are not consistent among
|
||||||
|
// platforms.
|
||||||
|
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||||
|
#elif !FMT_HAS_C99_STRFTIME
|
||||||
|
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||||
|
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d",
|
||||||
|
"%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"};
|
||||||
|
#endif
|
||||||
|
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
for (const auto& spec : spec_list) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
|
||||||
|
auto sys_output = system_strftime(spec, &tm);
|
||||||
|
|
||||||
|
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timezone formatters tests makes sense for localtime.
|
||||||
|
#if FMT_HAS_C99_STRFTIME
|
||||||
|
spec_list = {"%z", "%Z"};
|
||||||
|
#else
|
||||||
|
spec_list = {"%Z"};
|
||||||
|
#endif
|
||||||
|
for (const auto& spec : spec_list) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
|
||||||
|
auto sys_output = system_strftime(spec, &tm);
|
||||||
|
|
||||||
|
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||||
|
|
||||||
|
if (spec == "%z") {
|
||||||
|
sys_output.insert(sys_output.end() - 2, 1, ':');
|
||||||
|
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
||||||
|
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
||||||
|
// the timezone in std::tm (if it presents on platform).
|
||||||
|
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
|
||||||
|
std::vector<std::string> tz_names = {"GMT", "UTC"};
|
||||||
|
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
|
||||||
|
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
|
||||||
|
EXPECT_EQ("+0000", fmt::format("{:%z}", t1));
|
||||||
|
EXPECT_EQ("+0000", fmt::format("{:%z}", tm));
|
||||||
|
|
||||||
|
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", t1));
|
||||||
|
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm));
|
||||||
|
|
||||||
|
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1));
|
||||||
|
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_USE_LOCAL_TIME
|
||||||
|
|
||||||
|
TEST(chrono_test, localtime) {
|
||||||
|
auto t = std::time(nullptr);
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Duration>
|
||||||
|
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(
|
||||||
|
std::chrono::current_zone()->to_sys(tp));
|
||||||
auto tm = *std::localtime(&t);
|
auto tm = *std::localtime(&t);
|
||||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, time_point) {
|
TEST(chrono_test, local_system_clock_time_point) {
|
||||||
auto t1 = std::chrono::system_clock::now();
|
# ifdef _WIN32
|
||||||
EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
return; // Not supported on Windows.
|
||||||
EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1));
|
# endif
|
||||||
using time_point =
|
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
|
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
|
||||||
auto t2 = time_point(std::chrono::seconds(42));
|
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||||
EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
|
||||||
|
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
|
||||||
|
using time_point = std::chrono::local_time<std::chrono::seconds>;
|
||||||
|
auto t2 = time_point(std::chrono::seconds(86400 + 42));
|
||||||
|
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||||
|
|
||||||
std::vector<std::string> spec_list = {
|
std::vector<std::string> spec_list = {
|
||||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||||
|
@ -261,15 +378,20 @@ TEST(chrono_test, time_point) {
|
||||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||||
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
|
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
|
||||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
# ifndef _WIN32
|
||||||
#ifndef _WIN32
|
|
||||||
// Disabled on Windows because these formats are not consistent among
|
// Disabled on Windows because these formats are not consistent among
|
||||||
// platforms.
|
// platforms.
|
||||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||||
#endif
|
# elif !FMT_HAS_C99_STRFTIME
|
||||||
|
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||||
|
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
|
||||||
|
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
|
||||||
|
# endif
|
||||||
|
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
for (const auto& spec : spec_list) {
|
for (const auto& spec : spec_list) {
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
auto t = std::chrono::system_clock::to_time_t(
|
||||||
|
std::chrono::current_zone()->to_sys(t1));
|
||||||
auto tm = *std::localtime(&t);
|
auto tm = *std::localtime(&t);
|
||||||
|
|
||||||
auto sys_output = system_strftime(spec, &tm);
|
auto sys_output = system_strftime(spec, &tm);
|
||||||
|
@ -278,8 +400,26 @@ TEST(chrono_test, time_point) {
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
|
||||||
|
spec_list.cend()) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(
|
||||||
|
std::chrono::current_zone()->to_sys(t1));
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
|
||||||
|
auto sys_output = system_strftime("%z", &tm);
|
||||||
|
sys_output.insert(sys_output.end() - 2, 1, ':');
|
||||||
|
|
||||||
|
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
|
||||||
|
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
||||||
|
|
||||||
|
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
|
||||||
|
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // FMT_USE_LOCAL_TIME
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|
||||||
TEST(chrono_test, format_default) {
|
TEST(chrono_test, format_default) {
|
||||||
|
@ -324,7 +464,7 @@ TEST(chrono_test, format_default) {
|
||||||
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, align) {
|
TEST(chrono_test, duration_align) {
|
||||||
auto s = std::chrono::seconds(42);
|
auto s = std::chrono::seconds(42);
|
||||||
EXPECT_EQ("42s ", fmt::format("{:5}", s));
|
EXPECT_EQ("42s ", fmt::format("{:5}", s));
|
||||||
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
|
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
|
||||||
|
@ -340,6 +480,35 @@ TEST(chrono_test, align) {
|
||||||
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, tm_align) {
|
||||||
|
auto t = make_tm(1975, 12, 29, 12, 14, 16);
|
||||||
|
EXPECT_EQ("1975-12-29 12:14:16", fmt::format("{:%F %T}", t));
|
||||||
|
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:30%F %T}", t));
|
||||||
|
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:{}%F %T}", t, 30));
|
||||||
|
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:<30%F %T}", t));
|
||||||
|
EXPECT_EQ(" 1975-12-29 12:14:16 ", fmt::format("{:^30%F %T}", t));
|
||||||
|
EXPECT_EQ(" 1975-12-29 12:14:16", fmt::format("{:>30%F %T}", t));
|
||||||
|
|
||||||
|
EXPECT_EQ("1975-12-29 12:14:16***********", fmt::format("{:*<30%F %T}", t));
|
||||||
|
EXPECT_EQ("*****1975-12-29 12:14:16******", fmt::format("{:*^30%F %T}", t));
|
||||||
|
EXPECT_EQ("***********1975-12-29 12:14:16", fmt::format("{:*>30%F %T}", t));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, tp_align) {
|
||||||
|
auto tp = std::chrono::time_point_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::system_clock::from_time_t(0));
|
||||||
|
EXPECT_EQ("00:00.000000", fmt::format("{:%M:%S}", tp));
|
||||||
|
EXPECT_EQ("00:00.000000 ", fmt::format("{:15%M:%S}", tp));
|
||||||
|
EXPECT_EQ("00:00.000000 ", fmt::format("{:{}%M:%S}", tp, 15));
|
||||||
|
EXPECT_EQ("00:00.000000 ", fmt::format("{:<15%M:%S}", tp));
|
||||||
|
EXPECT_EQ(" 00:00.000000 ", fmt::format("{:^15%M:%S}", tp));
|
||||||
|
EXPECT_EQ(" 00:00.000000", fmt::format("{:>15%M:%S}", tp));
|
||||||
|
|
||||||
|
EXPECT_EQ("00:00.000000***", fmt::format("{:*<15%M:%S}", tp));
|
||||||
|
EXPECT_EQ("*00:00.000000**", fmt::format("{:*^15%M:%S}", tp));
|
||||||
|
EXPECT_EQ("***00:00.000000", fmt::format("{:*>15%M:%S}", tp));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(chrono_test, format_specs) {
|
TEST(chrono_test, format_specs) {
|
||||||
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
|
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
|
||||||
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
|
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
|
||||||
|
@ -407,6 +576,10 @@ TEST(chrono_test, invalid_specs) {
|
||||||
"invalid format");
|
"invalid format");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
|
||||||
"invalid format");
|
"invalid format");
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:abc}"), sec), fmt::format_error,
|
||||||
|
"invalid format");
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2f}"), sec), fmt::format_error,
|
||||||
|
"invalid format");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto format_tm(const std::tm& time, fmt::string_view spec,
|
auto format_tm(const std::tm& time, fmt::string_view spec,
|
||||||
|
@ -454,7 +627,7 @@ TEST(chrono_test, format_default_fp) {
|
||||||
|
|
||||||
TEST(chrono_test, format_precision) {
|
TEST(chrono_test, format_precision) {
|
||||||
EXPECT_THROW_MSG(
|
EXPECT_THROW_MSG(
|
||||||
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
|
(void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
|
||||||
fmt::format_error, "precision not allowed for this argument type");
|
fmt::format_error, "precision not allowed for this argument type");
|
||||||
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
|
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
|
||||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||||
|
@ -557,9 +730,9 @@ TEST(chrono_test, special_durations) {
|
||||||
"03:33");
|
"03:33");
|
||||||
EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
|
EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
|
||||||
"03:33:20");
|
"03:33:20");
|
||||||
EXPECT_EQ("44.000000000000",
|
EXPECT_EQ("01.234",
|
||||||
fmt::format("{:%S}", std::chrono::duration<float, std::pico>(
|
fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(
|
||||||
1.54213895E+26)));
|
1.234e12)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, unsigned_duration) {
|
TEST(chrono_test, unsigned_duration) {
|
||||||
|
@ -599,12 +772,28 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
|
||||||
"-13.420148734");
|
"-13.420148734");
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
|
||||||
|
// Check subsecond presision modifier.
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}),
|
||||||
|
"00.000001");
|
||||||
|
EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}),
|
||||||
|
"00.000001234000000000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6),
|
||||||
|
"00.000001");
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}),
|
||||||
|
"01.234000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
|
||||||
|
"-01.234000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
|
||||||
|
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
|
||||||
|
EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400");
|
||||||
{
|
{
|
||||||
// Check that {:%H:%M:%S} is equivalent to {:%T}.
|
// Check that {:%H:%M:%S} is equivalent to {:%T}.
|
||||||
auto dur = std::chrono::milliseconds{3601234};
|
auto dur = std::chrono::milliseconds{3601234};
|
||||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||||
EXPECT_EQ(formatted_dur, "01:00:01.234");
|
EXPECT_EQ(formatted_dur, "01:00:01.234");
|
||||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||||
|
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
||||||
}
|
}
|
||||||
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
||||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
||||||
|
@ -618,15 +807,197 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||||
auto formatted_dur = fmt::format("{:%T}", dur);
|
auto formatted_dur = fmt::format("{:%T}", dur);
|
||||||
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
|
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
|
||||||
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
|
||||||
|
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
|
||||||
}
|
}
|
||||||
// Check that durations with precision greater than std::chrono::seconds have
|
// Check that durations with precision greater than std::chrono::seconds have
|
||||||
// fixed precision, and print zeros even if there is no fractional part.
|
// fixed precision, and print zeros even if there is no fractional part.
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
||||||
"07.000000");
|
"07.000000");
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
EXPECT_EQ(fmt::format("{:%S}",
|
||||||
|
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
||||||
"00.333333");
|
"00.333333");
|
||||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)),
|
EXPECT_EQ(fmt::format("{:%S}",
|
||||||
|
std::chrono::duration<long long, std::ratio<1, 7>>(1)),
|
||||||
"00.142857");
|
"00.142857");
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{:%S}",
|
||||||
|
std::chrono::duration<signed char, std::ratio<1, 100>>(0x80)),
|
||||||
|
"-01.28");
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{:%M:%S}",
|
||||||
|
std::chrono::duration<short, std::ratio<1, 100>>(0x8000)),
|
||||||
|
"-05:27.68");
|
||||||
|
|
||||||
|
// Check that floating point seconds with ratio<1,1> are printed.
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
|
||||||
|
"01.500000");
|
||||||
|
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
|
||||||
|
"-01:01.250000");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|
||||||
|
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
|
||||||
|
// (time zone database) is not shipped with many windows versions.
|
||||||
|
#if FMT_USE_UTC_TIME && !defined(_WIN32)
|
||||||
|
TEST(chrono_test, utc_clock) {
|
||||||
|
auto t1 = std::chrono::system_clock::now();
|
||||||
|
auto t1_utc = std::chrono::utc_clock::from_sys(t1);
|
||||||
|
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t1),
|
||||||
|
fmt::format("{:%Y-%m-%d %H:%M:%S}", t1_utc));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST(chrono_test, timestamps_ratios) {
|
||||||
|
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
|
||||||
|
t1(std::chrono::milliseconds(67890));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%M:%S}", t1), "01:07.890");
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>
|
||||||
|
t2(std::chrono::minutes(7));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%M:%S}", t2), "07:00");
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::duration<int, std::ratio<9>>>
|
||||||
|
t3(std::chrono::duration<int, std::ratio<9>>(7));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%M:%S}", t3), "01:03");
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::duration<int, std::ratio<63>>>
|
||||||
|
t4(std::chrono::duration<int, std::ratio<63>>(1));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, timestamps_sub_seconds) {
|
||||||
|
std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::duration<long long, std::ratio<1, 3>>>
|
||||||
|
t1(std::chrono::duration<long long, std::ratio<1, 3>>(4));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t1), "01.333333");
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::duration<double, std::ratio<1, 3>>>
|
||||||
|
t2(std::chrono::duration<double, std::ratio<1, 3>>(4));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t2), "01.333333");
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
|
||||||
|
t3(std::chrono::seconds(2));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t3), "02");
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::duration<double>>
|
||||||
|
t4(std::chrono::duration<double, std::ratio<1, 1>>(9.5));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t4), "09.500000");
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::duration<double>>
|
||||||
|
t5(std::chrono::duration<double, std::ratio<1, 1>>(9));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t5), "09");
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::milliseconds>
|
||||||
|
t6(std::chrono::seconds(1) + std::chrono::milliseconds(120));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t6), "01.120");
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::microseconds>
|
||||||
|
t7(std::chrono::microseconds(1234567));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t7), "01.234567");
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::nanoseconds>
|
||||||
|
t8(std::chrono::nanoseconds(123456789));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
|
||||||
|
|
||||||
|
const auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
const auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
|
||||||
|
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
||||||
|
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
|
||||||
|
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
||||||
|
fmt::format("{:%Y-%m-%d %T}", t9));
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::milliseconds>
|
||||||
|
t10(std::chrono::milliseconds(2000));
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto epoch = std::chrono::time_point<std::chrono::system_clock,
|
||||||
|
std::chrono::milliseconds>();
|
||||||
|
const auto d = std::chrono::milliseconds(250);
|
||||||
|
|
||||||
|
EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - d));
|
||||||
|
EXPECT_EQ("00.000", fmt::format("{:%S}", epoch));
|
||||||
|
EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, glibc_extensions) {
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()),
|
||||||
|
fmt::format_error, "invalid format");
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()),
|
||||||
|
fmt::format_error, "invalid format");
|
||||||
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()),
|
||||||
|
fmt::format_error, "invalid format");
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto d = std::chrono::hours(1) + std::chrono::minutes(2) +
|
||||||
|
std::chrono::seconds(3);
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto tm = make_tm(1970, 1, 1, 1, 2, 3);
|
||||||
|
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3");
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140);
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", d), "03.140");
|
||||||
|
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto d = std::chrono::duration<double>(3.14);
|
||||||
|
EXPECT_EQ(fmt::format("{:%S}", d), "03.140000");
|
||||||
|
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000");
|
||||||
|
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000");
|
||||||
|
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Test if compile errors are produced where necessary.
|
# Test if compile errors are produced where necessary.
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.1...3.18)
|
cmake_minimum_required(VERSION 3.8...3.25)
|
||||||
project(compile-error-test CXX)
|
project(compile-error-test CXX)
|
||||||
|
|
||||||
set(fmt_headers "
|
set(fmt_headers "
|
||||||
|
@ -64,7 +64,7 @@ function (run_tests)
|
||||||
")
|
")
|
||||||
|
|
||||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
|
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
|
||||||
cmake_minimum_required(VERSION 3.1...3.18)
|
cmake_minimum_required(VERSION 3.8...3.25)
|
||||||
project(tests CXX)
|
project(tests CXX)
|
||||||
add_subdirectory(${FMT_DIR} fmt)
|
add_subdirectory(${FMT_DIR} fmt)
|
||||||
${cmake_targets}
|
${cmake_targets}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
|
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
|
||||||
defined(__cpp_constexpr_dynamic_alloc) && \
|
defined(__cpp_constexpr_dynamic_alloc) && \
|
||||||
__cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L
|
__cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L
|
||||||
|
|
||||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||||
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
||||||
|
|
|
@ -21,38 +21,6 @@ TEST(iterator_test, counting_iterator) {
|
||||||
EXPECT_EQ((it + 41).count(), 42);
|
EXPECT_EQ((it + 41).count(), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(iterator_test, truncating_iterator) {
|
|
||||||
char* p = nullptr;
|
|
||||||
auto it = fmt::detail::truncating_iterator<char*>(p, 3);
|
|
||||||
auto prev = it++;
|
|
||||||
EXPECT_EQ(prev.base(), p);
|
|
||||||
EXPECT_EQ(it.base(), p + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(iterator_test, truncating_iterator_default_construct) {
|
|
||||||
auto it = fmt::detail::truncating_iterator<char*>();
|
|
||||||
EXPECT_EQ(nullptr, it.base());
|
|
||||||
EXPECT_EQ(std::size_t{0}, it.count());
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cpp_lib_ranges
|
|
||||||
TEST(iterator_test, truncating_iterator_is_output_iterator) {
|
|
||||||
static_assert(
|
|
||||||
std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(iterator_test, truncating_back_inserter) {
|
|
||||||
auto buffer = std::string();
|
|
||||||
auto bi = std::back_inserter(buffer);
|
|
||||||
auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
|
|
||||||
*it++ = '4';
|
|
||||||
*it++ = '2';
|
|
||||||
*it++ = '1';
|
|
||||||
EXPECT_EQ(buffer.size(), 2);
|
|
||||||
EXPECT_EQ(buffer, "42");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(compile_test, compile_fallback) {
|
TEST(compile_test, compile_fallback) {
|
||||||
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
||||||
// not available.
|
// not available.
|
||||||
|
@ -227,10 +195,27 @@ TEST(compile_test, format_to_n) {
|
||||||
EXPECT_STREQ("2a", buffer);
|
EXPECT_STREQ("2a", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(compile_test, formatted_size) {
|
# ifdef __cpp_lib_bit_cast
|
||||||
EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42));
|
TEST(compile_test, constexpr_formatted_size) {
|
||||||
EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0));
|
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
||||||
|
EXPECT_EQ(size, 2);
|
||||||
|
FMT_CONSTEXPR20 size_t hex_size =
|
||||||
|
fmt::formatted_size(FMT_COMPILE("{:x}"), 15);
|
||||||
|
EXPECT_EQ(hex_size, 1);
|
||||||
|
FMT_CONSTEXPR20 size_t binary_size =
|
||||||
|
fmt::formatted_size(FMT_COMPILE("{:b}"), 15);
|
||||||
|
EXPECT_EQ(binary_size, 4);
|
||||||
|
FMT_CONSTEXPR20 size_t padded_size =
|
||||||
|
fmt::formatted_size(FMT_COMPILE("{:*^6}"), 42);
|
||||||
|
EXPECT_EQ(padded_size, 6);
|
||||||
|
FMT_CONSTEXPR20 size_t float_size =
|
||||||
|
fmt::formatted_size(FMT_COMPILE("{:.3}"), 12.345);
|
||||||
|
EXPECT_EQ(float_size, 4);
|
||||||
|
FMT_CONSTEXPR20 size_t str_size =
|
||||||
|
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
|
||||||
|
EXPECT_EQ(str_size, 3);
|
||||||
}
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
TEST(compile_test, text_and_arg) {
|
TEST(compile_test, text_and_arg) {
|
||||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||||
|
@ -300,8 +285,9 @@ TEST(compile_test, compile_format_string_literal) {
|
||||||
// (compiler file
|
// (compiler file
|
||||||
// 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp',
|
// 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp',
|
||||||
// line 8635)
|
// line 8635)
|
||||||
#if ((FMT_CPLUSPLUS >= 202002L) && \
|
#if ((FMT_CPLUSPLUS >= 202002L) && \
|
||||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
|
(!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \
|
||||||
|
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
|
||||||
(FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
|
(FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
|
||||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||||
|
|
|
@ -70,6 +70,16 @@ TEST(string_view_test, compare) {
|
||||||
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
||||||
EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
|
EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
|
||||||
EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
|
EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
|
||||||
|
|
||||||
|
EXPECT_TRUE(string_view("foo").starts_with('f'));
|
||||||
|
EXPECT_FALSE(string_view("foo").starts_with('o'));
|
||||||
|
EXPECT_FALSE(string_view().starts_with('o'));
|
||||||
|
|
||||||
|
EXPECT_TRUE(string_view("foo").starts_with("fo"));
|
||||||
|
EXPECT_TRUE(string_view("foo").starts_with("foo"));
|
||||||
|
EXPECT_FALSE(string_view("foo").starts_with("fooo"));
|
||||||
|
EXPECT_FALSE(string_view().starts_with("fooo"));
|
||||||
|
|
||||||
check_op<std::equal_to>();
|
check_op<std::equal_to>();
|
||||||
check_op<std::not_equal_to>();
|
check_op<std::not_equal_to>();
|
||||||
check_op<std::less>();
|
check_op<std::less>();
|
||||||
|
@ -78,24 +88,6 @@ TEST(string_view_test, compare) {
|
||||||
check_op<std::greater_equal>();
|
check_op<std::greater_equal>();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace test_ns {
|
|
||||||
template <typename Char> class test_string {
|
|
||||||
private:
|
|
||||||
std::basic_string<Char> s_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
test_string(const Char* s) : s_(s) {}
|
|
||||||
const Char* data() const { return s_.data(); }
|
|
||||||
size_t length() const { return s_.size(); }
|
|
||||||
operator const Char*() const { return s_.c_str(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
|
|
||||||
return {s.data(), s.length()};
|
|
||||||
}
|
|
||||||
} // namespace test_ns
|
|
||||||
|
|
||||||
TEST(core_test, is_output_iterator) {
|
TEST(core_test, is_output_iterator) {
|
||||||
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
|
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
|
||||||
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
|
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
|
||||||
|
@ -149,7 +141,7 @@ TEST(buffer_test, indestructible) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct mock_buffer final : buffer<T> {
|
template <typename T> struct mock_buffer final : buffer<T> {
|
||||||
MOCK_METHOD1(do_grow, size_t(size_t capacity));
|
MOCK_METHOD(size_t, do_grow, (size_t));
|
||||||
|
|
||||||
void grow(size_t capacity) override {
|
void grow(size_t capacity) override {
|
||||||
this->set(this->data(), do_grow(capacity));
|
this->set(this->data(), do_grow(capacity));
|
||||||
|
@ -281,7 +273,7 @@ struct custom_context {
|
||||||
bool called = false;
|
bool called = false;
|
||||||
|
|
||||||
template <typename T> struct formatter_type {
|
template <typename T> struct formatter_type {
|
||||||
auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,11 +290,11 @@ struct test_struct {};
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <typename Char> struct formatter<test_struct, Char> {
|
template <typename Char> struct formatter<test_struct, Char> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) {
|
auto format(test_struct, format_context& ctx) const -> decltype(ctx.out()) {
|
||||||
auto test = string_view("test");
|
auto test = string_view("test");
|
||||||
return std::copy_n(test.data(), test.size(), ctx.out());
|
return std::copy_n(test.data(), test.size(), ctx.out());
|
||||||
}
|
}
|
||||||
|
@ -316,7 +308,7 @@ TEST(arg_test, format_args) {
|
||||||
|
|
||||||
TEST(arg_test, make_value_with_custom_context) {
|
TEST(arg_test, make_value_with_custom_context) {
|
||||||
auto t = test_struct();
|
auto t = test_struct();
|
||||||
fmt::detail::value<custom_context> arg(
|
auto arg = fmt::detail::value<custom_context>(
|
||||||
fmt::detail::arg_mapper<custom_context>().map(t));
|
fmt::detail::arg_mapper<custom_context>().map(t));
|
||||||
auto ctx = custom_context();
|
auto ctx = custom_context();
|
||||||
auto parse_ctx = fmt::format_parse_context("");
|
auto parse_ctx = fmt::format_parse_context("");
|
||||||
|
@ -335,12 +327,12 @@ template <typename T> struct mock_visitor {
|
||||||
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
|
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_METHOD1_T(visit, test_result(T value));
|
MOCK_METHOD(test_result, visit, (T));
|
||||||
MOCK_METHOD0_T(unexpected, void());
|
MOCK_METHOD(void, unexpected, ());
|
||||||
|
|
||||||
test_result operator()(T value) { return visit(value); }
|
auto operator()(T value) -> test_result { return visit(value); }
|
||||||
|
|
||||||
template <typename U> test_result operator()(U) {
|
template <typename U> auto operator()(U) -> test_result {
|
||||||
unexpected();
|
unexpected();
|
||||||
return test_result();
|
return test_result();
|
||||||
}
|
}
|
||||||
|
@ -369,10 +361,11 @@ VISIT_TYPE(unsigned long, unsigned long long);
|
||||||
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
||||||
EXPECT_CALL(visitor, visit(expected)); \
|
EXPECT_CALL(visitor, visit(expected)); \
|
||||||
using iterator = std::back_insert_iterator<buffer<Char>>; \
|
using iterator = std::back_insert_iterator<buffer<Char>>; \
|
||||||
|
auto var = value; \
|
||||||
fmt::visit_format_arg( \
|
fmt::visit_format_arg( \
|
||||||
visitor, \
|
visitor, \
|
||||||
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
|
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
|
||||||
value)); \
|
var)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_ARG_SIMPLE(value) \
|
#define CHECK_ARG_SIMPLE(value) \
|
||||||
|
@ -392,13 +385,13 @@ using test_types =
|
||||||
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
||||||
|
|
||||||
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||||
T test_value() {
|
auto test_value() -> T {
|
||||||
return static_cast<T>(42);
|
return static_cast<T>(42);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T,
|
template <typename T,
|
||||||
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
|
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
|
||||||
T test_value() {
|
auto test_value() -> T {
|
||||||
return static_cast<T>(4.2);
|
return static_cast<T>(4.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,8 +434,8 @@ TEST(arg_test, pointer_arg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct check_custom {
|
struct check_custom {
|
||||||
test_result operator()(
|
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
|
||||||
fmt::basic_format_arg<fmt::format_context>::handle h) const {
|
-> test_result {
|
||||||
struct test_buffer final : fmt::detail::buffer<char> {
|
struct test_buffer final : fmt::detail::buffer<char> {
|
||||||
char data[10];
|
char data[10];
|
||||||
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
|
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
|
||||||
|
@ -461,13 +454,13 @@ TEST(arg_test, custom_arg) {
|
||||||
auto test = test_struct();
|
auto test = test_struct();
|
||||||
using visitor =
|
using visitor =
|
||||||
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
|
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
|
||||||
testing::StrictMock<visitor> v;
|
auto&& v = testing::StrictMock<visitor>();
|
||||||
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
|
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
|
||||||
fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
|
fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(arg_test, visit_invalid_arg) {
|
TEST(arg_test, visit_invalid_arg) {
|
||||||
testing::StrictMock<mock_visitor<fmt::monostate>> visitor;
|
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
|
||||||
EXPECT_CALL(visitor, visit(_));
|
EXPECT_CALL(visitor, visit(_));
|
||||||
auto arg = fmt::basic_format_arg<fmt::format_context>();
|
auto arg = fmt::basic_format_arg<fmt::format_context>();
|
||||||
fmt::visit_format_arg(visitor, arg);
|
fmt::visit_format_arg(visitor, arg);
|
||||||
|
@ -475,185 +468,78 @@ TEST(arg_test, visit_invalid_arg) {
|
||||||
|
|
||||||
#if FMT_USE_CONSTEXPR
|
#if FMT_USE_CONSTEXPR
|
||||||
|
|
||||||
enum class arg_id_result { none, empty, index, name, error };
|
enum class arg_id_result { none, empty, index, name };
|
||||||
struct test_arg_id_handler {
|
struct test_arg_id_handler {
|
||||||
arg_id_result res = arg_id_result::none;
|
arg_id_result res = arg_id_result::none;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
string_view name;
|
string_view name;
|
||||||
|
|
||||||
constexpr void operator()() { res = arg_id_result::empty; }
|
constexpr void on_auto() { res = arg_id_result::empty; }
|
||||||
|
|
||||||
constexpr void operator()(int i) {
|
constexpr void on_index(int i) {
|
||||||
res = arg_id_result::index;
|
res = arg_id_result::index;
|
||||||
index = i;
|
index = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void operator()(string_view n) {
|
constexpr void on_name(string_view n) {
|
||||||
res = arg_id_result::name;
|
res = arg_id_result::name;
|
||||||
name = n;
|
name = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void on_error(const char*) { res = arg_id_result::error; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
|
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
|
||||||
test_arg_id_handler h;
|
auto h = test_arg_id_handler();
|
||||||
fmt::detail::parse_arg_id(s, s + N, h);
|
fmt::detail::parse_arg_id(s, s + N, h);
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(format_test, constexpr_parse_arg_id) {
|
TEST(core_test, constexpr_parse_arg_id) {
|
||||||
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
|
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
|
||||||
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
|
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
|
||||||
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
|
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
|
||||||
static_assert(parse_arg_id("42:").index == 42, "");
|
static_assert(parse_arg_id("42:").index == 42, "");
|
||||||
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
|
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
|
||||||
static_assert(parse_arg_id("foo:").name.size() == 3, "");
|
static_assert(parse_arg_id("foo:").name.size() == 3, "");
|
||||||
static_assert(parse_arg_id("!").res == arg_id_result::error, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct test_format_specs_handler {
|
template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
|
||||||
enum result { none, hash, zero, loc, error };
|
auto ctx = fmt::detail::compile_parse_context<char>(fmt::string_view(s, N),
|
||||||
result res = none;
|
43, nullptr);
|
||||||
|
auto specs = fmt::detail::dynamic_format_specs<>();
|
||||||
fmt::align_t alignment = fmt::align::none;
|
fmt::detail::parse_format_specs(s, s + N - 1, specs, ctx,
|
||||||
fmt::sign_t sign = fmt::sign::none;
|
fmt::detail::type::float_type);
|
||||||
char fill = 0;
|
return specs;
|
||||||
int width = 0;
|
|
||||||
fmt::detail::arg_ref<char> width_ref;
|
|
||||||
int precision = 0;
|
|
||||||
fmt::detail::arg_ref<char> precision_ref;
|
|
||||||
fmt::presentation_type type = fmt::presentation_type::none;
|
|
||||||
|
|
||||||
// Workaround for MSVC2017 bug that results in "expression did not evaluate
|
|
||||||
// to a constant" with compiler-generated copy ctor.
|
|
||||||
constexpr test_format_specs_handler() {}
|
|
||||||
constexpr test_format_specs_handler(const test_format_specs_handler& other) =
|
|
||||||
default;
|
|
||||||
|
|
||||||
constexpr void on_align(fmt::align_t a) { alignment = a; }
|
|
||||||
constexpr void on_fill(fmt::string_view f) { fill = f[0]; }
|
|
||||||
constexpr void on_sign(fmt::sign_t s) { sign = s; }
|
|
||||||
constexpr void on_hash() { res = hash; }
|
|
||||||
constexpr void on_zero() { res = zero; }
|
|
||||||
constexpr void on_localized() { res = loc; }
|
|
||||||
|
|
||||||
constexpr void on_width(int w) { width = w; }
|
|
||||||
constexpr void on_dynamic_width(fmt::detail::auto_id) {}
|
|
||||||
constexpr void on_dynamic_width(int index) { width_ref = index; }
|
|
||||||
constexpr void on_dynamic_width(string_view) {}
|
|
||||||
|
|
||||||
constexpr void on_precision(int p) { precision = p; }
|
|
||||||
constexpr void on_dynamic_precision(fmt::detail::auto_id) {}
|
|
||||||
constexpr void on_dynamic_precision(int index) { precision_ref = index; }
|
|
||||||
constexpr void on_dynamic_precision(string_view) {}
|
|
||||||
|
|
||||||
constexpr void end_precision() {}
|
|
||||||
constexpr void on_type(fmt::presentation_type t) { type = t; }
|
|
||||||
constexpr void on_error(const char*) { res = error; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t N>
|
|
||||||
constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) {
|
|
||||||
auto h = test_format_specs_handler();
|
|
||||||
fmt::detail::parse_format_specs(s, s + N - 1, h);
|
|
||||||
return h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(core_test, constexpr_parse_format_specs) {
|
TEST(core_test, constexpr_parse_format_specs) {
|
||||||
using handler = test_format_specs_handler;
|
static_assert(parse_test_specs("<").align == fmt::align::left, "");
|
||||||
static_assert(parse_test_specs("<").alignment == fmt::align::left, "");
|
static_assert(parse_test_specs("*^").fill[0] == '*', "");
|
||||||
static_assert(parse_test_specs("*^").fill == '*', "");
|
|
||||||
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
|
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
|
||||||
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
|
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
|
||||||
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
|
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
|
||||||
static_assert(parse_test_specs("#").res == handler::hash, "");
|
static_assert(parse_test_specs("#").alt, "");
|
||||||
static_assert(parse_test_specs("0").res == handler::zero, "");
|
static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
|
||||||
static_assert(parse_test_specs("L").res == handler::loc, "");
|
static_assert(parse_test_specs("L").localized, "");
|
||||||
static_assert(parse_test_specs("42").width == 42, "");
|
static_assert(parse_test_specs("42").width == 42, "");
|
||||||
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
|
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
|
||||||
static_assert(parse_test_specs(".42").precision == 42, "");
|
static_assert(parse_test_specs(".42").precision == 42, "");
|
||||||
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
|
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
|
||||||
static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, "");
|
static_assert(
|
||||||
static_assert(parse_test_specs("{<").res == handler::error, "");
|
parse_test_specs("f").type == fmt::presentation_type::fixed_lower, "");
|
||||||
}
|
|
||||||
|
|
||||||
struct test_parse_context {
|
|
||||||
using char_type = char;
|
|
||||||
|
|
||||||
constexpr int next_arg_id() { return 11; }
|
|
||||||
template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
|
|
||||||
|
|
||||||
constexpr const char* begin() { return nullptr; }
|
|
||||||
constexpr const char* end() { return nullptr; }
|
|
||||||
|
|
||||||
void on_error(const char*) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t N>
|
|
||||||
constexpr fmt::detail::dynamic_format_specs<char> parse_dynamic_specs(
|
|
||||||
const char (&s)[N]) {
|
|
||||||
auto specs = fmt::detail::dynamic_format_specs<char>();
|
|
||||||
auto ctx = test_parse_context();
|
|
||||||
auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx);
|
|
||||||
parse_format_specs(s, s + N - 1, h);
|
|
||||||
return specs;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(format_test, constexpr_dynamic_specs_handler) {
|
|
||||||
static_assert(parse_dynamic_specs("<").align == fmt::align::left, "");
|
|
||||||
static_assert(parse_dynamic_specs("*^").fill[0] == '*', "");
|
|
||||||
static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, "");
|
|
||||||
static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, "");
|
|
||||||
static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, "");
|
|
||||||
static_assert(parse_dynamic_specs("#").alt, "");
|
|
||||||
static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, "");
|
|
||||||
static_assert(parse_dynamic_specs("42").width == 42, "");
|
|
||||||
static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, "");
|
|
||||||
static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, "");
|
|
||||||
static_assert(parse_dynamic_specs(".42").precision == 42, "");
|
|
||||||
static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, "");
|
|
||||||
static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, "");
|
|
||||||
static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec,
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <size_t N>
|
|
||||||
constexpr test_format_specs_handler check_specs(const char (&s)[N]) {
|
|
||||||
fmt::detail::specs_checker<test_format_specs_handler> checker(
|
|
||||||
test_format_specs_handler(), fmt::detail::type::double_type);
|
|
||||||
parse_format_specs(s, s + N - 1, checker);
|
|
||||||
return checker;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(format_test, constexpr_specs_checker) {
|
|
||||||
using handler = test_format_specs_handler;
|
|
||||||
static_assert(check_specs("<").alignment == fmt::align::left, "");
|
|
||||||
static_assert(check_specs("*^").fill == '*', "");
|
|
||||||
static_assert(check_specs("+").sign == fmt::sign::plus, "");
|
|
||||||
static_assert(check_specs("-").sign == fmt::sign::minus, "");
|
|
||||||
static_assert(check_specs(" ").sign == fmt::sign::space, "");
|
|
||||||
static_assert(check_specs("#").res == handler::hash, "");
|
|
||||||
static_assert(check_specs("0").res == handler::zero, "");
|
|
||||||
static_assert(check_specs("42").width == 42, "");
|
|
||||||
static_assert(check_specs("{42}").width_ref.val.index == 42, "");
|
|
||||||
static_assert(check_specs(".42").precision == 42, "");
|
|
||||||
static_assert(check_specs(".{42}").precision_ref.val.index == 42, "");
|
|
||||||
static_assert(check_specs("d").type == fmt::presentation_type::dec, "");
|
|
||||||
static_assert(check_specs("{<").res == handler::error, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct test_format_string_handler {
|
struct test_format_string_handler {
|
||||||
constexpr void on_text(const char*, const char*) {}
|
constexpr void on_text(const char*, const char*) {}
|
||||||
|
|
||||||
constexpr int on_arg_id() { return 0; }
|
constexpr auto on_arg_id() -> int { return 0; }
|
||||||
|
|
||||||
template <typename T> constexpr int on_arg_id(T) { return 0; }
|
template <typename T> constexpr auto on_arg_id(T) -> int { return 0; }
|
||||||
|
|
||||||
constexpr void on_replacement_field(int, const char*) {}
|
constexpr void on_replacement_field(int, const char*) {}
|
||||||
|
|
||||||
constexpr const char* on_format_specs(int, const char* begin, const char*) {
|
constexpr auto on_format_specs(int, const char* begin, const char*) -> const
|
||||||
|
char* {
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,7 +554,7 @@ template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
|
||||||
return !h.error;
|
return !h.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(format_test, constexpr_parse_format_string) {
|
TEST(core_test, constexpr_parse_format_string) {
|
||||||
static_assert(parse_string("foo"), "");
|
static_assert(parse_string("foo"), "");
|
||||||
static_assert(!parse_string("}"), "");
|
static_assert(!parse_string("}"), "");
|
||||||
static_assert(parse_string("{}"), "");
|
static_assert(parse_string("{}"), "");
|
||||||
|
@ -687,19 +573,20 @@ struct disabled_formatter_convertible {
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct formatter<enabled_formatter> {
|
template <> struct formatter<enabled_formatter> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) {
|
auto format(enabled_formatter, format_context& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
return ctx.out();
|
return ctx.out();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct formatter<enabled_ptr_formatter*> {
|
template <> struct formatter<enabled_ptr_formatter*> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
auto format(enabled_ptr_formatter*, format_context& ctx)
|
auto format(enabled_ptr_formatter*, format_context& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
return ctx.out();
|
return ctx.out();
|
||||||
}
|
}
|
||||||
|
@ -720,7 +607,7 @@ struct nonconst_formattable {};
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct formatter<const_formattable> {
|
template <> struct formatter<const_formattable> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -732,7 +619,7 @@ template <> struct formatter<const_formattable> {
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct formatter<nonconst_formattable> {
|
template <> struct formatter<nonconst_formattable> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,7 +641,7 @@ struct convertible_to_pointer_formattable {
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct formatter<convertible_to_pointer_formattable> {
|
template <> struct formatter<convertible_to_pointer_formattable> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -768,29 +655,17 @@ FMT_END_NAMESPACE
|
||||||
|
|
||||||
enum class unformattable_scoped_enum {};
|
enum class unformattable_scoped_enum {};
|
||||||
|
|
||||||
namespace test {
|
|
||||||
enum class formattable_scoped_enum {};
|
|
||||||
auto format_as(formattable_scoped_enum) -> int { return 42; }
|
|
||||||
|
|
||||||
struct convertible_to_enum {
|
|
||||||
operator formattable_scoped_enum() const { return {}; }
|
|
||||||
};
|
|
||||||
} // namespace test
|
|
||||||
|
|
||||||
TEST(core_test, is_formattable) {
|
TEST(core_test, is_formattable) {
|
||||||
#if 0
|
|
||||||
// This should be enabled once corresponding map overloads are gone.
|
|
||||||
static_assert(fmt::is_formattable<signed char*>::value, "");
|
|
||||||
static_assert(fmt::is_formattable<unsigned char*>::value, "");
|
|
||||||
static_assert(fmt::is_formattable<const signed char*>::value, "");
|
|
||||||
static_assert(fmt::is_formattable<const unsigned char*>::value, "");
|
|
||||||
#endif
|
|
||||||
static_assert(!fmt::is_formattable<wchar_t>::value, "");
|
static_assert(!fmt::is_formattable<wchar_t>::value, "");
|
||||||
#ifdef __cpp_char8_t
|
#ifdef __cpp_char8_t
|
||||||
static_assert(!fmt::is_formattable<char8_t>::value, "");
|
static_assert(!fmt::is_formattable<char8_t>::value, "");
|
||||||
#endif
|
#endif
|
||||||
static_assert(!fmt::is_formattable<char16_t>::value, "");
|
static_assert(!fmt::is_formattable<char16_t>::value, "");
|
||||||
static_assert(!fmt::is_formattable<char32_t>::value, "");
|
static_assert(!fmt::is_formattable<char32_t>::value, "");
|
||||||
|
static_assert(!fmt::is_formattable<signed char*>::value, "");
|
||||||
|
static_assert(!fmt::is_formattable<unsigned char*>::value, "");
|
||||||
|
static_assert(!fmt::is_formattable<const signed char*>::value, "");
|
||||||
|
static_assert(!fmt::is_formattable<const unsigned char*>::value, "");
|
||||||
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
|
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
|
||||||
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
|
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
|
||||||
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
|
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
|
||||||
|
@ -798,7 +673,8 @@ TEST(core_test, is_formattable) {
|
||||||
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
|
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
|
||||||
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
|
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
|
||||||
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
|
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
|
||||||
static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, "");
|
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
|
||||||
|
"");
|
||||||
|
|
||||||
static_assert(fmt::is_formattable<const_formattable&>::value, "");
|
static_assert(fmt::is_formattable<const_formattable&>::value, "");
|
||||||
static_assert(fmt::is_formattable<const const_formattable&>::value, "");
|
static_assert(fmt::is_formattable<const const_formattable&>::value, "");
|
||||||
|
@ -818,45 +694,47 @@ TEST(core_test, is_formattable) {
|
||||||
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
|
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
|
||||||
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
|
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
|
||||||
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
||||||
static_assert(fmt::is_formattable<test::formattable_scoped_enum>::value, "");
|
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
|
||||||
static_assert(!fmt::is_formattable<test::convertible_to_enum>::value, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
|
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
|
||||||
|
|
||||||
TEST(core_test, format_to) {
|
TEST(core_test, format_to) {
|
||||||
std::string s;
|
auto s = std::string();
|
||||||
fmt::format_to(std::back_inserter(s), "{}", 42);
|
fmt::format_to(std::back_inserter(s), "{}", 42);
|
||||||
EXPECT_EQ(s, "42");
|
EXPECT_EQ(s, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(core_test, format_as) {
|
#ifdef __cpp_lib_byte
|
||||||
EXPECT_EQ(fmt::format("{}", test::formattable_scoped_enum()), "42");
|
TEST(core_test, format_byte) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
struct convertible_to_int {
|
struct convertible_to_int {
|
||||||
operator int() const { return 42; }
|
operator int() const { return 42; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct convertible_to_c_string {
|
struct convertible_to_cstring {
|
||||||
operator const char*() const { return "foo"; }
|
operator const char*() const { return "foo"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct formatter<convertible_to_int> {
|
template <> struct formatter<convertible_to_int> {
|
||||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) {
|
auto format(convertible_to_int, format_context& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
return std::copy_n("foo", 3, ctx.out());
|
return std::copy_n("foo", 3, ctx.out());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct formatter<convertible_to_c_string> {
|
template <> struct formatter<convertible_to_cstring> {
|
||||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
auto format(convertible_to_c_string, format_context& ctx)
|
auto format(convertible_to_cstring, format_context& ctx) const
|
||||||
-> decltype(ctx.out()) {
|
-> decltype(ctx.out()) {
|
||||||
return std::copy_n("bar", 3, ctx.out());
|
return std::copy_n("bar", 3, ctx.out());
|
||||||
}
|
}
|
||||||
|
@ -865,7 +743,7 @@ FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(core_test, formatter_overrides_implicit_conversion) {
|
TEST(core_test, formatter_overrides_implicit_conversion) {
|
||||||
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
|
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
|
||||||
EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar");
|
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that check is not found by ADL.
|
// Test that check is not found by ADL.
|
||||||
|
@ -874,27 +752,26 @@ TEST(core_test, adl_check) {
|
||||||
EXPECT_EQ(fmt::format("{}", test_struct()), "test");
|
EXPECT_EQ(fmt::format("{}", test_struct()), "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(core_test, to_string_view_foreign_strings) {
|
|
||||||
using namespace test_ns;
|
|
||||||
EXPECT_EQ(to_string_view(test_string<char>("42")), "42");
|
|
||||||
fmt::detail::type type =
|
|
||||||
fmt::detail::mapped_type_constant<test_string<char>,
|
|
||||||
fmt::format_context>::value;
|
|
||||||
EXPECT_EQ(type, fmt::detail::type::string_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct implicitly_convertible_to_string {
|
|
||||||
operator std::string() const { return "foo"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct implicitly_convertible_to_string_view {
|
struct implicitly_convertible_to_string_view {
|
||||||
operator fmt::string_view() const { return "foo"; }
|
operator fmt::string_view() const { return "foo"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(core_test, format_implicitly_convertible_to_string_view) {
|
TEST(core_test, no_implicit_conversion_to_string_view) {
|
||||||
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
|
EXPECT_FALSE(
|
||||||
|
fmt::is_formattable<implicitly_convertible_to_string_view>::value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef FMT_USE_STRING_VIEW
|
||||||
|
struct implicitly_convertible_to_std_string_view {
|
||||||
|
operator std::string_view() const { return "foo"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(core_test, no_implicit_conversion_to_std_string_view) {
|
||||||
|
EXPECT_FALSE(
|
||||||
|
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// std::is_constructible is broken in MSVC until version 2015.
|
// std::is_constructible is broken in MSVC until version 2015.
|
||||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
|
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
|
||||||
struct explicitly_convertible_to_string_view {
|
struct explicitly_convertible_to_string_view {
|
||||||
|
@ -923,25 +800,6 @@ TEST(core_test, format_explicitly_convertible_to_std_string_view) {
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct convertible_to_long_long {
|
|
||||||
operator long long() const { return 1LL << 32; }
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(format_test, format_convertible_to_long_long) {
|
|
||||||
EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long()));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct disabled_rvalue_conversion {
|
|
||||||
operator const char*() const& { return "foo"; }
|
|
||||||
operator const char*() & { return "foo"; }
|
|
||||||
operator const char*() const&& = delete;
|
|
||||||
operator const char*() && = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(core_test, disabled_rvalue_conversion) {
|
|
||||||
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace adl_test {
|
namespace adl_test {
|
||||||
template <typename... T> void make_format_args(const T&...) = delete;
|
template <typename... T> void make_format_args(const T&...) = delete;
|
||||||
|
|
||||||
|
@ -973,3 +831,28 @@ TEST(core_test, has_const_formatter) {
|
||||||
TEST(core_test, format_nonconst) {
|
TEST(core_test, format_nonconst) {
|
||||||
EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test");
|
EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct its_a_trap {
|
||||||
|
template <typename T> operator T() const {
|
||||||
|
auto v = T();
|
||||||
|
v.x = 42;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<its_a_trap> {
|
||||||
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(its_a_trap, format_context& ctx) const -> decltype(ctx.out()) {
|
||||||
|
auto s = string_view("42");
|
||||||
|
return std::copy(s.begin(), s.end(), ctx.out());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(core_test, trappy_conversion) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", its_a_trap()), "42");
|
||||||
|
}
|
||||||
|
|
|
@ -8,12 +8,14 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
|
||||||
#include "fmt/chrono.h"
|
#include "fmt/chrono.h"
|
||||||
#include "fmt/color.h"
|
#include "fmt/color.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "fmt/ostream.h"
|
#include "fmt/ostream.h"
|
||||||
#include "fmt/ranges.h"
|
#include "fmt/ranges.h"
|
||||||
#include "fmt/xchar.h"
|
#include "fmt/xchar.h"
|
||||||
|
#undef I
|
||||||
|
|
||||||
// Exercise the API to verify that everything we expect to can compile.
|
// Exercise the API to verify that everything we expect to can compile.
|
||||||
void test_format_api() {
|
void test_format_api() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.1...3.18)
|
cmake_minimum_required(VERSION 3.8...3.25)
|
||||||
|
|
||||||
project(fmt-test)
|
project(fmt-test)
|
||||||
|
|
||||||
|
|
|
@ -196,43 +196,6 @@ TEST(fp_test, multiply) {
|
||||||
EXPECT_EQ(v.e, 4 + 8 + 64);
|
EXPECT_EQ(v.e, 4 + 8 + 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(fp_test, get_cached_power) {
|
|
||||||
using limits = std::numeric_limits<double>;
|
|
||||||
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
|
|
||||||
int dec_exp = 0;
|
|
||||||
auto power = fmt::detail::get_cached_power(exp, dec_exp);
|
|
||||||
bigint exact, cache(power.f);
|
|
||||||
if (dec_exp >= 0) {
|
|
||||||
exact.assign_pow10(dec_exp);
|
|
||||||
if (power.e <= 0)
|
|
||||||
exact <<= -power.e;
|
|
||||||
else
|
|
||||||
cache <<= power.e;
|
|
||||||
exact.align(cache);
|
|
||||||
cache.align(exact);
|
|
||||||
auto exact_str = fmt::to_string(exact);
|
|
||||||
auto cache_str = fmt::to_string(cache);
|
|
||||||
EXPECT_EQ(exact_str.size(), cache_str.size());
|
|
||||||
EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
|
|
||||||
int diff = cache_str[15] - exact_str[15];
|
|
||||||
if (diff == 1)
|
|
||||||
EXPECT_GT(exact_str[16], '8');
|
|
||||||
else
|
|
||||||
EXPECT_EQ(diff, 0);
|
|
||||||
} else {
|
|
||||||
cache.assign_pow10(-dec_exp);
|
|
||||||
cache *= power.f + 1; // Inexact check.
|
|
||||||
exact = 1;
|
|
||||||
exact <<= -power.e;
|
|
||||||
exact.align(cache);
|
|
||||||
auto exact_str = fmt::to_string(exact);
|
|
||||||
auto cache_str = fmt::to_string(cache);
|
|
||||||
EXPECT_EQ(exact_str.size(), cache_str.size());
|
|
||||||
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(fp_test, dragonbox_max_k) {
|
TEST(fp_test, dragonbox_max_k) {
|
||||||
using fmt::detail::dragonbox::floor_log10_pow2;
|
using fmt::detail::dragonbox::floor_log10_pow2;
|
||||||
using float_info = fmt::detail::dragonbox::float_info<float>;
|
using float_info = fmt::detail::dragonbox::float_info<float>;
|
||||||
|
@ -242,66 +205,18 @@ TEST(fp_test, dragonbox_max_k) {
|
||||||
floor_log10_pow2(std::numeric_limits<float>::min_exponent -
|
floor_log10_pow2(std::numeric_limits<float>::min_exponent -
|
||||||
fmt::detail::num_significand_bits<float>() - 1));
|
fmt::detail::num_significand_bits<float>() - 1));
|
||||||
using double_info = fmt::detail::dragonbox::float_info<double>;
|
using double_info = fmt::detail::dragonbox::float_info<double>;
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(fmt::detail::const_check(double_info::max_k),
|
||||||
fmt::detail::const_check(double_info::max_k),
|
double_info::kappa -
|
||||||
double_info::kappa -
|
floor_log10_pow2(
|
||||||
floor_log10_pow2(std::numeric_limits<double>::min_exponent -
|
std::numeric_limits<double>::min_exponent -
|
||||||
fmt::detail::num_significand_bits<double>() - 1));
|
2 * fmt::detail::num_significand_bits<double>() - 1));
|
||||||
}
|
|
||||||
|
|
||||||
TEST(fp_test, get_round_direction) {
|
|
||||||
using fmt::detail::get_round_direction;
|
|
||||||
using fmt::detail::round_direction;
|
|
||||||
EXPECT_EQ(get_round_direction(100, 50, 0), round_direction::down);
|
|
||||||
EXPECT_EQ(get_round_direction(100, 51, 0), round_direction::up);
|
|
||||||
EXPECT_EQ(get_round_direction(100, 40, 10), round_direction::down);
|
|
||||||
EXPECT_EQ(get_round_direction(100, 60, 10), round_direction::up);
|
|
||||||
for (size_t i = 41; i < 60; ++i)
|
|
||||||
EXPECT_EQ(get_round_direction(100, i, 10), round_direction::unknown);
|
|
||||||
uint64_t max = max_value<uint64_t>();
|
|
||||||
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
|
|
||||||
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
|
|
||||||
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
|
|
||||||
// Check that remainder + error doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(max, max - 1, 2), round_direction::up);
|
|
||||||
// Check that 2 * (remainder + error) doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(max, max / 2 + 1, max / 2),
|
|
||||||
round_direction::unknown);
|
|
||||||
// Check that remainder - error doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(100, 40, 41), round_direction::unknown);
|
|
||||||
// Check that 2 * (remainder - error) doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(max, max - 1, 1), round_direction::up);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(fp_test, fixed_handler) {
|
|
||||||
struct handler : fmt::detail::gen_digits_handler {
|
|
||||||
char buffer[10];
|
|
||||||
handler(int prec = 0) : fmt::detail::gen_digits_handler() {
|
|
||||||
buf = buffer;
|
|
||||||
precision = prec;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handler().on_digit('0', 100, 99, 0, false);
|
|
||||||
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure);
|
|
||||||
namespace digits = fmt::detail::digits;
|
|
||||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error);
|
|
||||||
// Check that divisor - error doesn't overflow.
|
|
||||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error);
|
|
||||||
// Check that 2 * error doesn't overflow.
|
|
||||||
uint64_t max = max_value<uint64_t>();
|
|
||||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
|
|
||||||
auto buf = fmt::memory_buffer();
|
|
||||||
format_float(0.42, -1, fmt::detail::float_specs(), buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(format_impl_test, format_error_code) {
|
TEST(format_impl_test, format_error_code) {
|
||||||
std::string msg = "error 42", sep = ": ";
|
std::string msg = "error 42", sep = ": ";
|
||||||
{
|
{
|
||||||
auto buffer = fmt::memory_buffer();
|
auto buffer = fmt::memory_buffer();
|
||||||
format_to(fmt::appender(buffer), "garbage");
|
fmt::format_to(fmt::appender(buffer), "garbage");
|
||||||
fmt::detail::format_error_code(buffer, 42, "test");
|
fmt::detail::format_error_code(buffer, 42, "test");
|
||||||
EXPECT_EQ(to_string(buffer), "test: " + msg);
|
EXPECT_EQ(to_string(buffer), "test: " + msg);
|
||||||
}
|
}
|
||||||
|
@ -353,6 +268,16 @@ TEST(format_impl_test, count_digits) {
|
||||||
test_count_digits<uint64_t>();
|
test_count_digits<uint64_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(format_impl_test, countl_zero) {
|
||||||
|
constexpr auto num_bits = fmt::detail::num_bits<uint32_t>();
|
||||||
|
uint32_t n = 1u;
|
||||||
|
for (int i = 1; i < num_bits - 1; i++) {
|
||||||
|
n <<= 1;
|
||||||
|
EXPECT_EQ(fmt::detail::countl_zero(n - 1), num_bits - i);
|
||||||
|
EXPECT_EQ(fmt::detail::countl_zero(n), num_bits - i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if FMT_USE_FLOAT128
|
#if FMT_USE_FLOAT128
|
||||||
TEST(format_impl_test, write_float128) {
|
TEST(format_impl_test, write_float128) {
|
||||||
auto s = std::string();
|
auto s = std::string();
|
||||||
|
@ -372,10 +297,22 @@ struct double_double {
|
||||||
auto operator-() const -> double_double { return double_double(-a, -b); }
|
auto operator-() const -> double_double { return double_double(-a, -b); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto format_as(double_double d) -> double { return d; }
|
||||||
|
|
||||||
bool operator>=(const double_double& lhs, const double_double& rhs) {
|
bool operator>=(const double_double& lhs, const double_double& rhs) {
|
||||||
return lhs.a + lhs.b >= rhs.a + rhs.b;
|
return lhs.a + lhs.b >= rhs.a + rhs.b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct slow_float {
|
||||||
|
float value;
|
||||||
|
|
||||||
|
explicit constexpr slow_float(float val = 0) : value(val) {}
|
||||||
|
operator float() const { return value; }
|
||||||
|
auto operator-() const -> slow_float { return slow_float(-value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
auto format_as(slow_float f) -> float { return f; }
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
template <> struct is_floating_point<double_double> : std::true_type {};
|
template <> struct is_floating_point<double_double> : std::true_type {};
|
||||||
template <> struct numeric_limits<double_double> {
|
template <> struct numeric_limits<double_double> {
|
||||||
|
@ -383,14 +320,35 @@ template <> struct numeric_limits<double_double> {
|
||||||
static constexpr bool is_iec559 = true;
|
static constexpr bool is_iec559 = true;
|
||||||
static constexpr int digits = 106;
|
static constexpr int digits = 106;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <> struct is_floating_point<slow_float> : std::true_type {};
|
||||||
|
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
namespace detail {
|
||||||
|
template <> struct is_fast_float<slow_float> : std::false_type {};
|
||||||
|
namespace dragonbox {
|
||||||
|
template <> struct float_info<slow_float> {
|
||||||
|
using carrier_uint = uint32_t;
|
||||||
|
static const int exponent_bits = 8;
|
||||||
|
};
|
||||||
|
} // namespace dragonbox
|
||||||
|
} // namespace detail
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(format_impl_test, write_double_double) {
|
TEST(format_impl_test, write_double_double) {
|
||||||
auto s = std::string();
|
auto s = std::string();
|
||||||
fmt::detail::write<char>(std::back_inserter(s), double_double(42), {});
|
fmt::detail::write<char>(std::back_inserter(s), double_double(42), {});
|
||||||
#ifndef _MSC_VER // MSVC has an issue with specializing is_floating_point.
|
// Specializing is_floating_point is broken in MSVC.
|
||||||
EXPECT_EQ(s, "42");
|
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "42");
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
TEST(format_impl_test, write_dragon_even) {
|
||||||
|
auto s = std::string();
|
||||||
|
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
|
||||||
|
// Specializing is_floating_point is broken in MSVC.
|
||||||
|
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -401,3 +359,125 @@ TEST(format_impl_test, write_console_signature) {
|
||||||
(void)p;
|
(void)p;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// A public domain branchless UTF-8 decoder by Christopher Wellons:
|
||||||
|
// https://github.com/skeeto/branchless-utf8
|
||||||
|
constexpr bool unicode_is_surrogate(uint32_t c) {
|
||||||
|
return c >= 0xD800U && c <= 0xDFFFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
|
||||||
|
if (c >= (1UL << 16)) {
|
||||||
|
s[0] = static_cast<char>(0xf0 | (c >> 18));
|
||||||
|
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));
|
||||||
|
s[2] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
|
||||||
|
s[3] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
|
||||||
|
return s + 4;
|
||||||
|
} else if (c >= (1UL << 11)) {
|
||||||
|
s[0] = static_cast<char>(0xe0 | (c >> 12));
|
||||||
|
s[1] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
|
||||||
|
s[2] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
|
||||||
|
return s + 3;
|
||||||
|
} else if (c >= (1UL << 7)) {
|
||||||
|
s[0] = static_cast<char>(0xc0 | (c >> 6));
|
||||||
|
s[1] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
|
||||||
|
return s + 2;
|
||||||
|
} else {
|
||||||
|
s[0] = static_cast<char>(c);
|
||||||
|
return s + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure it can decode every character
|
||||||
|
TEST(format_impl_test, utf8_decode_decode_all) {
|
||||||
|
for (uint32_t i = 0; i < 0x10ffff; i++) {
|
||||||
|
if (!unicode_is_surrogate(i)) {
|
||||||
|
int e;
|
||||||
|
uint32_t c;
|
||||||
|
char buf[8] = {0};
|
||||||
|
char* end = utf8_encode(buf, i);
|
||||||
|
const char* res = fmt::detail::utf8_decode(buf, &c, &e);
|
||||||
|
EXPECT_EQ(end, res);
|
||||||
|
EXPECT_EQ(c, i);
|
||||||
|
EXPECT_EQ(e, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject everything outside of U+0000..U+10FFFF
|
||||||
|
TEST(format_impl_test, utf8_decode_out_of_range) {
|
||||||
|
for (uint32_t i = 0x110000; i < 0x1fffff; i++) {
|
||||||
|
int e;
|
||||||
|
uint32_t c;
|
||||||
|
char buf[8] = {0};
|
||||||
|
utf8_encode(buf, i);
|
||||||
|
const char* end = fmt::detail::utf8_decode(buf, &c, &e);
|
||||||
|
EXPECT_NE(e, 0);
|
||||||
|
EXPECT_EQ(end - buf, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does it reject all surrogate halves?
|
||||||
|
TEST(format_impl_test, utf8_decode_surrogate_halves) {
|
||||||
|
for (uint32_t i = 0xd800; i <= 0xdfff; i++) {
|
||||||
|
int e;
|
||||||
|
uint32_t c;
|
||||||
|
char buf[8] = {0};
|
||||||
|
utf8_encode(buf, i);
|
||||||
|
fmt::detail::utf8_decode(buf, &c, &e);
|
||||||
|
EXPECT_NE(e, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// How about non-canonical encodings?
|
||||||
|
TEST(format_impl_test, utf8_decode_non_canonical_encodings) {
|
||||||
|
int e;
|
||||||
|
uint32_t c;
|
||||||
|
const char* end;
|
||||||
|
|
||||||
|
char buf2[8] = {char(0xc0), char(0xA4)};
|
||||||
|
end = fmt::detail::utf8_decode(buf2, &c, &e);
|
||||||
|
EXPECT_NE(e, 0); // non-canonical len 2
|
||||||
|
EXPECT_EQ(end, buf2 + 2); // non-canonical recover 2
|
||||||
|
|
||||||
|
char buf3[8] = {char(0xe0), char(0x80), char(0xA4)};
|
||||||
|
end = fmt::detail::utf8_decode(buf3, &c, &e);
|
||||||
|
EXPECT_NE(e, 0); // non-canonical len 3
|
||||||
|
EXPECT_EQ(end, buf3 + 3); // non-canonical recover 3
|
||||||
|
|
||||||
|
char buf4[8] = {char(0xf0), char(0x80), char(0x80), char(0xA4)};
|
||||||
|
end = fmt::detail::utf8_decode(buf4, &c, &e);
|
||||||
|
EXPECT_NE(e, 0); // non-canonical encoding len 4
|
||||||
|
EXPECT_EQ(end, buf4 + 4); // non-canonical recover 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's try some bogus byte sequences
|
||||||
|
TEST(format_impl_test, utf8_decode_bogus_byte_sequences) {
|
||||||
|
int e;
|
||||||
|
uint32_t c;
|
||||||
|
|
||||||
|
// Invalid first byte
|
||||||
|
char buf0[4] = {char(0xff)};
|
||||||
|
auto len = fmt::detail::utf8_decode(buf0, &c, &e) - buf0;
|
||||||
|
EXPECT_NE(e, 0); // "bogus [ff] 0x%02x U+%04lx", e, (unsigned long)c);
|
||||||
|
EXPECT_EQ(len, 1); // "bogus [ff] recovery %d", len);
|
||||||
|
|
||||||
|
// Invalid first byte
|
||||||
|
char buf1[4] = {char(0x80)};
|
||||||
|
len = fmt::detail::utf8_decode(buf1, &c, &e) - buf1;
|
||||||
|
EXPECT_NE(e, 0); // "bogus [80] 0x%02x U+%04lx", e, (unsigned long)c);
|
||||||
|
EXPECT_EQ(len, 1); // "bogus [80] recovery %d", len);
|
||||||
|
|
||||||
|
// Looks like a two-byte sequence but second byte is wrong
|
||||||
|
char buf2[4] = {char(0xc0), char(0x0a)};
|
||||||
|
len = fmt::detail::utf8_decode(buf2, &c, &e) - buf2;
|
||||||
|
EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c
|
||||||
|
EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(format_impl_test, to_utf8) {
|
||||||
|
auto s = std::string("ёжик");
|
||||||
|
auto u = fmt::detail::to_utf8<wchar_t>(L"\x0451\x0436\x0438\x043A");
|
||||||
|
EXPECT_EQ(s, u.str());
|
||||||
|
EXPECT_EQ(s.size(), u.size());
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,7 +22,7 @@ function(add_fuzzer source)
|
||||||
if (FMT_FUZZ_LDFLAGS)
|
if (FMT_FUZZ_LDFLAGS)
|
||||||
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
|
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
|
||||||
endif ()
|
endif ()
|
||||||
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
|
target_compile_features(${name} PRIVATE cxx_std_14)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)
|
foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)
|
||||||
|
|
|
@ -201,6 +201,7 @@ TEST(gtest_extra_test, expect_write_streaming) {
|
||||||
// EXPECT_THROW_MSG macro.
|
// EXPECT_THROW_MSG macro.
|
||||||
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
(void)n;
|
||||||
using std::runtime_error;
|
using std::runtime_error;
|
||||||
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
|
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
|
||||||
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
|
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
|
||||||
|
@ -213,6 +214,7 @@ TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
||||||
// EXPECT_SYSTEM_ERROR macro.
|
// EXPECT_SYSTEM_ERROR macro.
|
||||||
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
|
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
(void)n;
|
||||||
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
|
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
|
||||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
|
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
|
||||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), "");
|
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), "");
|
||||||
|
|
|
@ -12,12 +12,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifdef FMT_MODULE_TEST
|
#include "fmt/os.h"
|
||||||
import fmt;
|
|
||||||
#else
|
|
||||||
# include "fmt/os.h"
|
|
||||||
#endif // FMG_MODULE_TEST
|
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
||||||
|
|
|
@ -9,6 +9,7 @@ add_library(gtest STATIC
|
||||||
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
|
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
|
||||||
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
|
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
|
||||||
target_include_directories(gtest SYSTEM PUBLIC .)
|
target_include_directories(gtest SYSTEM PUBLIC .)
|
||||||
|
target_compile_features(gtest PUBLIC cxx_std_11)
|
||||||
|
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
if (Threads_FOUND)
|
if (Threads_FOUND)
|
||||||
|
@ -17,13 +18,6 @@ else ()
|
||||||
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
|
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Workaround GTest bug https://github.com/google/googletest/issues/705.
|
|
||||||
fmt_check_cxx_compiler_flag(
|
|
||||||
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
|
||||||
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
|
|
||||||
target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
||||||
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
|
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
|
@ -2202,7 +2202,7 @@ bool UnitTestOptions::MatchesFilter(const std::string& name_str,
|
||||||
|
|
||||||
// Check if this pattern matches name_str.
|
// Check if this pattern matches name_str.
|
||||||
if (PatternMatchesString(name_str, pattern, pattern_end)) {
|
if (PatternMatchesString(name_str, pattern, pattern_end)) {
|
||||||
return true;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give up on this pattern. However, if we found a pattern separator (:),
|
// Give up on this pattern. However, if we found a pattern separator (:),
|
||||||
|
@ -13778,7 +13778,7 @@ UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith(
|
||||||
|
|
||||||
UntypedActionResultHolderBase* result = nullptr;
|
UntypedActionResultHolderBase* result = nullptr;
|
||||||
|
|
||||||
auto perform_action = [&] {
|
auto perform_action = [&, this] {
|
||||||
return untyped_action == nullptr
|
return untyped_action == nullptr
|
||||||
? this->UntypedPerformDefaultAction(untyped_args, ss.str())
|
? this->UntypedPerformDefaultAction(untyped_args, ss.str())
|
||||||
: this->UntypedPerformAction(untyped_action, untyped_args);
|
: this->UntypedPerformAction(untyped_action, untyped_args);
|
||||||
|
|
|
@ -20,8 +20,8 @@ template <typename T> class mock_allocator {
|
||||||
mock_allocator() {}
|
mock_allocator() {}
|
||||||
mock_allocator(const mock_allocator&) {}
|
mock_allocator(const mock_allocator&) {}
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
MOCK_METHOD1_T(allocate, T*(size_t n));
|
MOCK_METHOD(T*, allocate, (size_t));
|
||||||
MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
|
MOCK_METHOD(void, deallocate, (T*, size_t));
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Allocator> class allocator_ref {
|
template <typename Allocator> class allocator_ref {
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
# define FMT_HIDE_MODULE_BUGS
|
# define FMT_HIDE_MODULE_BUGS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define FMT_MODULE_TEST
|
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
@ -41,7 +39,6 @@
|
||||||
#else
|
#else
|
||||||
# define FMT_POSIX(call) call
|
# define FMT_POSIX(call) call
|
||||||
#endif
|
#endif
|
||||||
#define FMT_OS_H_ // don't pull in os.h directly or indirectly
|
|
||||||
|
|
||||||
import fmt;
|
import fmt;
|
||||||
|
|
||||||
|
@ -53,13 +50,8 @@ static bool macro_leaked =
|
||||||
false;
|
false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Include sources to pick up functions and classes from the module rather than
|
#define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly
|
||||||
// from the non-modular library which is baked into the 'test-main' library.
|
#include "gtest-extra.h"
|
||||||
// This averts linker problems:
|
|
||||||
// - strong ownership model: missing linker symbols
|
|
||||||
// - weak ownership model: duplicate linker symbols
|
|
||||||
#include "gtest-extra.cc"
|
|
||||||
#include "util.cc"
|
|
||||||
|
|
||||||
// an implicitly exported namespace must be visible [module.interface]/2.2
|
// an implicitly exported namespace must be visible [module.interface]/2.2
|
||||||
TEST(module_test, namespace) {
|
TEST(module_test, namespace) {
|
||||||
|
@ -75,9 +67,8 @@ bool oops_detail_namespace_is_visible;
|
||||||
namespace fmt {
|
namespace fmt {
|
||||||
bool namespace_detail_invisible() {
|
bool namespace_detail_invisible() {
|
||||||
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
|
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
|
||||||
((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \
|
_MSC_FULL_VER <= 193700000
|
||||||
(_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704))
|
// bug in msvc up to at least 17.7:
|
||||||
// bug in msvc up to 16.11.5 / 17.0-pre5:
|
|
||||||
|
|
||||||
// the namespace is visible even when it is neither
|
// the namespace is visible even when it is neither
|
||||||
// implicitly nor explicitly exported
|
// implicitly nor explicitly exported
|
||||||
|
@ -111,7 +102,7 @@ TEST(module_test, macros) {
|
||||||
// but rather visibility of all client-facing overloads, reachability of
|
// but rather visibility of all client-facing overloads, reachability of
|
||||||
// non-exported entities, name lookup and overload resolution within
|
// non-exported entities, name lookup and overload resolution within
|
||||||
// template instantitions.
|
// template instantitions.
|
||||||
// Excercise all exported entities of the API at least once.
|
// Exercise all exported entities of the API at least once.
|
||||||
// Instantiate as many code paths as possible.
|
// Instantiate as many code paths as possible.
|
||||||
|
|
||||||
TEST(module_test, to_string) {
|
TEST(module_test, to_string) {
|
||||||
|
@ -140,7 +131,7 @@ TEST(module_test, format_to) {
|
||||||
EXPECT_EQ("42", std::string_view(buffer));
|
EXPECT_EQ("42", std::string_view(buffer));
|
||||||
|
|
||||||
fmt::memory_buffer mb;
|
fmt::memory_buffer mb;
|
||||||
fmt::format_to(mb, "{}", 42);
|
fmt::format_to(std::back_inserter(mb), "{}", 42);
|
||||||
EXPECT_EQ("42", std::string_view(buffer));
|
EXPECT_EQ("42", std::string_view(buffer));
|
||||||
|
|
||||||
std::wstring w;
|
std::wstring w;
|
||||||
|
@ -152,7 +143,7 @@ TEST(module_test, format_to) {
|
||||||
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||||
|
|
||||||
fmt::wmemory_buffer wb;
|
fmt::wmemory_buffer wb;
|
||||||
fmt::format_to(wb, L"{}", 42);
|
fmt::format_to(std::back_inserter(wb), L"{}", 42);
|
||||||
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,8 +202,8 @@ TEST(module_test, dynamic_format_args) {
|
||||||
|
|
||||||
TEST(module_test, vformat) {
|
TEST(module_test, vformat) {
|
||||||
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
|
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
|
||||||
EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"),
|
EXPECT_EQ(L"42",
|
||||||
fmt::make_wformat_args(42)));
|
fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, vformat_to) {
|
TEST(module_test, vformat_to) {
|
||||||
|
@ -245,9 +236,9 @@ TEST(module_test, vformat_to_n) {
|
||||||
auto wstore = fmt::make_wformat_args(12345);
|
auto wstore = fmt::make_wformat_args(12345);
|
||||||
std::wstring w;
|
std::wstring w;
|
||||||
auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1,
|
auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1,
|
||||||
fmt::to_string_view(L"{}"), wstore);
|
fmt::wstring_view(L"{}"), wstore);
|
||||||
wchar_t wbuffer[4] = {0};
|
wchar_t wbuffer[4] = {0};
|
||||||
fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore);
|
fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string as_string(std::wstring_view text) {
|
std::string as_string(std::wstring_view text) {
|
||||||
|
@ -258,22 +249,18 @@ std::string as_string(std::wstring_view text) {
|
||||||
TEST(module_test, print) {
|
TEST(module_test, print) {
|
||||||
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
|
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
|
||||||
EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ");
|
EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ");
|
||||||
if (false) {
|
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
|
||||||
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
|
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
|
||||||
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, vprint) {
|
TEST(module_test, vprint) {
|
||||||
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
|
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
|
||||||
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
|
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
|
||||||
"4.2");
|
"4.2");
|
||||||
if (false) {
|
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
|
||||||
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
|
as_string(L"42µ"));
|
||||||
as_string(L"42µ"));
|
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
|
||||||
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
|
as_string(L"42"));
|
||||||
as_string(L"42"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, named_args) {
|
TEST(module_test, named_args) {
|
||||||
|
@ -284,9 +271,7 @@ TEST(module_test, named_args) {
|
||||||
TEST(module_test, literals) {
|
TEST(module_test, literals) {
|
||||||
using namespace fmt::literals;
|
using namespace fmt::literals;
|
||||||
EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42));
|
EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42));
|
||||||
EXPECT_EQ("42", "{}"_format(42));
|
|
||||||
EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42));
|
EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42));
|
||||||
EXPECT_EQ(L"42", L"{}"_format(42));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, locale) {
|
TEST(module_test, locale) {
|
||||||
|
@ -320,7 +305,7 @@ TEST(module_test, string_view) {
|
||||||
|
|
||||||
TEST(module_test, memory_buffer) {
|
TEST(module_test, memory_buffer) {
|
||||||
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
|
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
|
||||||
fmt::format_to(buffer, "{}", "42");
|
fmt::format_to(std::back_inserter(buffer), "{}", "42");
|
||||||
EXPECT_EQ("42", to_string(buffer));
|
EXPECT_EQ("42", to_string(buffer));
|
||||||
fmt::memory_buffer nbuffer(std::move(buffer));
|
fmt::memory_buffer nbuffer(std::move(buffer));
|
||||||
EXPECT_EQ("42", to_string(nbuffer));
|
EXPECT_EQ("42", to_string(nbuffer));
|
||||||
|
@ -395,27 +380,20 @@ struct test_formatter : fmt::formatter<char> {
|
||||||
bool check() { return true; }
|
bool check() { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct test_dynamic_formatter : fmt::dynamic_formatter<> {
|
TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); }
|
||||||
bool check() { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(module_test, formatter) {
|
|
||||||
EXPECT_TRUE(test_formatter{}.check());
|
|
||||||
EXPECT_TRUE(test_dynamic_formatter{}.check());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(module_test, join) {
|
TEST(module_test, join) {
|
||||||
int arr[3] = {1, 2, 3};
|
int arr[3] = {1, 2, 3};
|
||||||
std::vector<double> vec{1.0, 2.0, 3.0};
|
std::vector<double> vec{1.0, 2.0, 3.0};
|
||||||
std::initializer_list<int> il{1, 2, 3};
|
std::initializer_list<int> il{1, 2, 3};
|
||||||
auto sep = fmt::to_string_view(", ");
|
auto sep = fmt::string_view(", ");
|
||||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep)));
|
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep)));
|
||||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep)));
|
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep)));
|
||||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep)));
|
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep)));
|
||||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep)));
|
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep)));
|
||||||
EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep)));
|
EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep)));
|
||||||
|
|
||||||
auto wsep = fmt::to_string_view(L", ");
|
auto wsep = fmt::wstring_view(L", ");
|
||||||
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep)));
|
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep)));
|
||||||
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep)));
|
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep)));
|
||||||
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep)));
|
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep)));
|
||||||
|
@ -426,7 +404,6 @@ TEST(module_test, time) {
|
||||||
EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120);
|
EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120);
|
||||||
EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120);
|
EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120);
|
||||||
auto chrono_now = std::chrono::system_clock::now();
|
auto chrono_now = std::chrono::system_clock::now();
|
||||||
EXPECT_TRUE(fmt::localtime(chrono_now).tm_year > 120);
|
|
||||||
EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120);
|
EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,34 +430,16 @@ TEST(module_test, weekday) {
|
||||||
EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
|
EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, to_string_view) {
|
|
||||||
using fmt::to_string_view;
|
|
||||||
fmt::string_view nsv{to_string_view("42")};
|
|
||||||
EXPECT_EQ("42", nsv);
|
|
||||||
fmt::wstring_view wsv{to_string_view(L"42")};
|
|
||||||
EXPECT_EQ(L"42", wsv);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(module_test, printf) {
|
TEST(module_test, printf) {
|
||||||
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
|
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
|
||||||
EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42");
|
EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42");
|
||||||
if (false) {
|
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), as_string(L"42.123456"));
|
||||||
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456),
|
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
|
||||||
as_string(L"42.123456"));
|
|
||||||
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, fprintf) {
|
TEST(module_test, fprintf) {
|
||||||
EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42");
|
EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42");
|
||||||
std::ostringstream os;
|
|
||||||
fmt::fprintf(os, "%s", "bla");
|
|
||||||
EXPECT_EQ("bla", os.str());
|
|
||||||
|
|
||||||
EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42"));
|
EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42"));
|
||||||
std::wostringstream ws;
|
|
||||||
fmt::fprintf(ws, L"%s", L"bla");
|
|
||||||
EXPECT_EQ(L"bla", ws.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, sprintf) {
|
TEST(module_test, sprintf) {
|
||||||
|
@ -490,25 +449,15 @@ TEST(module_test, sprintf) {
|
||||||
|
|
||||||
TEST(module_test, vprintf) {
|
TEST(module_test, vprintf) {
|
||||||
EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42");
|
EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42");
|
||||||
if (false) {
|
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
|
||||||
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
|
as_string(L"42"));
|
||||||
as_string(L"42"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, vfprintf) {
|
TEST(module_test, vfprintf) {
|
||||||
auto args = fmt::make_printf_args(42);
|
auto args = fmt::make_printf_args(42);
|
||||||
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42");
|
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42");
|
||||||
std::ostringstream os;
|
|
||||||
fmt::vfprintf(os, "%d", args);
|
|
||||||
EXPECT_EQ("42", os.str());
|
|
||||||
auto wargs = fmt::make_wprintf_args(42);
|
auto wargs = fmt::make_wprintf_args(42);
|
||||||
if (false) {
|
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
|
||||||
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
|
|
||||||
}
|
|
||||||
std::wostringstream ws;
|
|
||||||
fmt::vfprintf(ws, L"%d", wargs);
|
|
||||||
EXPECT_EQ(L"42", ws.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, vsprintf) {
|
TEST(module_test, vsprintf) {
|
||||||
|
@ -538,8 +487,13 @@ TEST(module_test, buffered_file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(module_test, output_file) {
|
TEST(module_test, output_file) {
|
||||||
|
#ifdef __clang__
|
||||||
|
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
|
||||||
|
"Clang 16.0 emits multiple copies of vtables");
|
||||||
|
#else
|
||||||
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
|
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
|
||||||
out.close();
|
out.close();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
struct custom_context {
|
struct custom_context {
|
||||||
|
@ -552,21 +506,15 @@ TEST(module_test, custom_context) {
|
||||||
EXPECT_TRUE(!custom_arg);
|
EXPECT_TRUE(!custom_arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct disabled_formatter {};
|
|
||||||
|
|
||||||
TEST(module_test, has_formatter) {
|
|
||||||
EXPECT_FALSE(
|
|
||||||
(fmt::has_formatter<disabled_formatter, fmt::format_context>::value));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(module_test, is_formattable) {
|
|
||||||
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(module_test, compile_format_string) {
|
TEST(module_test, compile_format_string) {
|
||||||
using namespace fmt::literals;
|
using namespace fmt::literals;
|
||||||
|
#ifdef __clang__
|
||||||
|
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
|
||||||
|
"Clang 16.0 fails to import user-defined literals");
|
||||||
|
#else
|
||||||
EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42));
|
EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42));
|
||||||
EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42));
|
EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42));
|
||||||
EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2));
|
EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2));
|
||||||
EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42"));
|
EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42"));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,60 +22,6 @@ using wstring_view = fmt::basic_string_view<wchar_t>;
|
||||||
|
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
|
|
||||||
TEST(util_test, utf16_to_utf8) {
|
|
||||||
auto s = std::string("ёжик");
|
|
||||||
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
|
|
||||||
EXPECT_EQ(s, u.str());
|
|
||||||
EXPECT_EQ(s.size(), u.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(util_test, utf16_to_utf8_empty_string) {
|
|
||||||
std::string s = "";
|
|
||||||
fmt::detail::utf16_to_utf8 u(L"");
|
|
||||||
EXPECT_EQ(s, u.str());
|
|
||||||
EXPECT_EQ(s.size(), u.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Converter, typename Char>
|
|
||||||
void check_utf_conversion_error(const char* message,
|
|
||||||
fmt::basic_string_view<Char> str =
|
|
||||||
fmt::basic_string_view<Char>(nullptr, 1)) {
|
|
||||||
fmt::memory_buffer out;
|
|
||||||
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
|
|
||||||
auto error = std::system_error(std::error_code());
|
|
||||||
try {
|
|
||||||
(Converter)(str);
|
|
||||||
} catch (const std::system_error& e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value());
|
|
||||||
EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(util_test, utf16_to_utf8_error) {
|
|
||||||
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
|
|
||||||
"cannot convert string from UTF-16 to UTF-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(util_test, utf16_to_utf8_convert) {
|
|
||||||
fmt::detail::utf16_to_utf8 u;
|
|
||||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1)));
|
|
||||||
EXPECT_EQ(ERROR_INVALID_PARAMETER,
|
|
||||||
u.convert(wstring_view(L"foo", INT_MAX + 1u)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(os_test, format_std_error_code) {
|
|
||||||
EXPECT_EQ("generic:42",
|
|
||||||
fmt::format(FMT_STRING("{0}"),
|
|
||||||
std::error_code(42, std::generic_category())));
|
|
||||||
EXPECT_EQ("system:42",
|
|
||||||
fmt::format(FMT_STRING("{0}"),
|
|
||||||
std::error_code(42, fmt::system_category())));
|
|
||||||
EXPECT_EQ("system:-42",
|
|
||||||
fmt::format(FMT_STRING("{0}"),
|
|
||||||
std::error_code(-42, fmt::system_category())));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(os_test, format_windows_error) {
|
TEST(os_test, format_windows_error) {
|
||||||
LPWSTR message = nullptr;
|
LPWSTR message = nullptr;
|
||||||
auto result = FormatMessageW(
|
auto result = FormatMessageW(
|
||||||
|
@ -83,7 +29,8 @@ TEST(os_test, format_windows_error) {
|
||||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
reinterpret_cast<LPWSTR>(&message), 0, nullptr);
|
reinterpret_cast<LPWSTR>(&message), 0, nullptr);
|
||||||
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
|
auto utf8_message =
|
||||||
|
fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2));
|
||||||
LocalFree(message);
|
LocalFree(message);
|
||||||
fmt::memory_buffer actual_message;
|
fmt::memory_buffer actual_message;
|
||||||
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
|
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
|
||||||
|
@ -108,7 +55,8 @@ TEST(os_test, format_long_windows_error) {
|
||||||
LocalFree(message);
|
LocalFree(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
|
auto utf8_message =
|
||||||
|
fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2));
|
||||||
LocalFree(message);
|
LocalFree(message);
|
||||||
fmt::memory_buffer actual_message;
|
fmt::memory_buffer actual_message;
|
||||||
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
|
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
|
||||||
|
@ -139,6 +87,17 @@ TEST(os_test, report_windows_error) {
|
||||||
fmt::to_string(out));
|
fmt::to_string(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# if FMT_USE_FCNTL && !defined(__MINGW32__)
|
||||||
|
TEST(file_test, open_windows_file) {
|
||||||
|
using fmt::file;
|
||||||
|
file out = file::open_windows_file(L"test-file",
|
||||||
|
file::WRONLY | file::CREATE | file::TRUNC);
|
||||||
|
out.write("x", 1);
|
||||||
|
file in = file::open_windows_file(L"test-file", file::RDONLY);
|
||||||
|
EXPECT_READ(in, "x");
|
||||||
|
}
|
||||||
|
# endif // FMT_USE_FCNTL && !defined(__MINGW32__)
|
||||||
|
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
|
|
|
@ -86,13 +86,13 @@ TEST(ostream_test, format_specs) {
|
||||||
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
|
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
|
||||||
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
|
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()),
|
||||||
format_error, "format specifier requires numeric argument");
|
format_error, "invalid format specifier");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()),
|
||||||
format_error, "format specifier requires numeric argument");
|
format_error, "invalid format specifier");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()),
|
||||||
format_error, "format specifier requires numeric argument");
|
format_error, "invalid format specifier");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()),
|
||||||
format_error, "format specifier requires numeric argument");
|
format_error, "invalid format specifier");
|
||||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
|
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
|
||||||
format_error, "format specifier requires numeric argument");
|
format_error, "format specifier requires numeric argument");
|
||||||
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
|
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
|
||||||
|
@ -106,9 +106,17 @@ TEST(ostream_test, empty_custom_output) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ostream_test, print) {
|
TEST(ostream_test, print) {
|
||||||
std::ostringstream os;
|
{
|
||||||
fmt::print(os, "Don't {}!", "panic");
|
std::ostringstream os;
|
||||||
EXPECT_EQ("Don't panic!", os.str());
|
fmt::print(os, "Don't {}!", "panic");
|
||||||
|
EXPECT_EQ("Don't panic!", os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
fmt::println(os, "Don't {}!", "panic");
|
||||||
|
EXPECT_EQ("Don't panic!\n", os.str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ostream_test, write_to_ostream) {
|
TEST(ostream_test, write_to_ostream) {
|
||||||
|
@ -132,7 +140,7 @@ TEST(ostream_test, write_to_ostream_max_size) {
|
||||||
} buffer(max_size);
|
} buffer(max_size);
|
||||||
|
|
||||||
struct mock_streambuf : std::streambuf {
|
struct mock_streambuf : std::streambuf {
|
||||||
MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n));
|
MOCK_METHOD(std::streamsize, xsputn, (const void*, std::streamsize));
|
||||||
auto xsputn(const char* s, std::streamsize n) -> std::streamsize override {
|
auto xsputn(const char* s, std::streamsize n) -> std::streamsize override {
|
||||||
const void* v = s;
|
const void* v = s;
|
||||||
return xsputn(v, n);
|
return xsputn(v, n);
|
||||||
|
@ -218,45 +226,6 @@ TEST(ostream_test, format_to_n) {
|
||||||
EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
|
EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct convertible {
|
|
||||||
T value;
|
|
||||||
explicit convertible(const T& val) : value(val) {}
|
|
||||||
operator T() const { return value; }
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(ostream_test, disable_builtin_ostream_operators) {
|
|
||||||
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
|
|
||||||
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct streamable_and_convertible_to_bool {
|
|
||||||
operator bool() const { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
|
|
||||||
return os << "foo";
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ostream_test, format_convertible_to_bool) {
|
|
||||||
// operator<< is intentionally not used because of potential ODR violations.
|
|
||||||
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct streamable_and_convertible_to_string_view {
|
|
||||||
operator fmt::string_view() const { return "foo"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os,
|
|
||||||
streamable_and_convertible_to_string_view) {
|
|
||||||
return os << "bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ostream_test, format_convertible_to_string_vew) {
|
|
||||||
// operator<< is intentionally not used because of potential ODR violations.
|
|
||||||
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_string_view()),
|
|
||||||
"foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct copyfmt_test {};
|
struct copyfmt_test {};
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, copyfmt_test) {
|
std::ostream& operator<<(std::ostream& os, copyfmt_test) {
|
||||||
|
|
|
@ -72,12 +72,6 @@ int test::open(const char* path, int oflag, int mode) {
|
||||||
EMULATE_EINTR(open, -1);
|
EMULATE_EINTR(open, -1);
|
||||||
return ::open(path, oflag, mode);
|
return ::open(path, oflag, mode);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag,
|
|
||||||
int pmode) {
|
|
||||||
EMULATE_EINTR(open, EINTR);
|
|
||||||
return _sopen_s(pfh, filename, oflag, shflag, pmode);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -220,11 +214,11 @@ TEST(os_test, getpagesize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(file_test, open_retry) {
|
TEST(file_test, open_retry) {
|
||||||
|
# ifndef _WIN32
|
||||||
write_file("temp", "there must be something here");
|
write_file("temp", "there must be something here");
|
||||||
std::unique_ptr<file> f{nullptr};
|
std::unique_ptr<file> f{nullptr};
|
||||||
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
|
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
|
||||||
"cannot open file temp");
|
"cannot open file temp");
|
||||||
# ifndef _WIN32
|
|
||||||
char c = 0;
|
char c = 0;
|
||||||
f->read(&c, 1);
|
f->read(&c, 1);
|
||||||
# endif
|
# endif
|
||||||
|
|
|
@ -37,8 +37,6 @@ int fstat(int fd, struct stat* buf);
|
||||||
#else
|
#else
|
||||||
typedef unsigned size_t;
|
typedef unsigned size_t;
|
||||||
typedef int ssize_t;
|
typedef int ssize_t;
|
||||||
errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag,
|
|
||||||
int pmode);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
|
|
@ -225,10 +225,8 @@ TEST(printf_test, hash_flag) {
|
||||||
EXPECT_PRINTF("-42.0000", "%#g", -42.0);
|
EXPECT_PRINTF("-42.0000", "%#g", -42.0);
|
||||||
EXPECT_PRINTF("-42.0000", "%#G", -42.0);
|
EXPECT_PRINTF("-42.0000", "%#G", -42.0);
|
||||||
|
|
||||||
safe_sprintf(buffer, "%#a", 16.0);
|
EXPECT_PRINTF("0x1.p+4", "%#a", 16.0);
|
||||||
EXPECT_PRINTF(buffer, "%#a", 16.0);
|
EXPECT_PRINTF("0X1.P+4", "%#A", 16.0);
|
||||||
safe_sprintf(buffer, "%#A", 16.0);
|
|
||||||
EXPECT_PRINTF(buffer, "%#A", 16.0);
|
|
||||||
|
|
||||||
// '#' flag is ignored for non-numeric types.
|
// '#' flag is ignored for non-numeric types.
|
||||||
EXPECT_PRINTF("x", "%#c", 'x');
|
EXPECT_PRINTF("x", "%#c", 'x');
|
||||||
|
@ -239,7 +237,7 @@ TEST(printf_test, width) {
|
||||||
|
|
||||||
// Width cannot be specified twice.
|
// Width cannot be specified twice.
|
||||||
EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
|
||||||
"invalid type specifier");
|
"invalid format specifier");
|
||||||
|
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
|
||||||
"number is too big");
|
"number is too big");
|
||||||
|
@ -407,10 +405,7 @@ TEST(printf_test, length) {
|
||||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(printf_test, bool) {
|
TEST(printf_test, bool) { EXPECT_PRINTF("1", "%d", true); }
|
||||||
EXPECT_PRINTF("1", "%d", true);
|
|
||||||
EXPECT_PRINTF("true", "%s", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(printf_test, int) {
|
TEST(printf_test, int) {
|
||||||
EXPECT_PRINTF("-42", "%d", -42);
|
EXPECT_PRINTF("-42", "%d", -42);
|
||||||
|
@ -534,11 +529,12 @@ TEST(printf_test, wide_string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(printf_test, vprintf) {
|
TEST(printf_test, vprintf) {
|
||||||
fmt::format_arg_store<fmt::printf_context, int> as{42};
|
int n = 42;
|
||||||
fmt::basic_format_args<fmt::printf_context> args(as);
|
auto store = fmt::format_arg_store<fmt::printf_context, int>(n);
|
||||||
EXPECT_EQ(fmt::vsprintf("%d", args), "42");
|
auto args = fmt::basic_format_args<fmt::printf_context>(store);
|
||||||
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
|
EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42");
|
||||||
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
|
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args),
|
||||||
|
"42");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
|
@ -554,31 +550,11 @@ TEST(printf_test, fixed_large_exponent) {
|
||||||
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
|
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(printf_test, vsprintf_make_args_example) {
|
TEST(printf_test, make_printf_args) {
|
||||||
fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
|
|
||||||
"something"};
|
|
||||||
fmt::basic_format_args<fmt::printf_context> args(as);
|
|
||||||
EXPECT_EQ("[42] something happened", fmt::vsprintf("[%d] %s happened", args));
|
|
||||||
auto as2 = fmt::make_printf_args(42, "something");
|
|
||||||
fmt::basic_format_args<fmt::printf_context> args2(as2);
|
|
||||||
EXPECT_EQ("[42] something happened",
|
EXPECT_EQ("[42] something happened",
|
||||||
fmt::vsprintf("[%d] %s happened", args2));
|
fmt::vsprintf(fmt::string_view("[%d] %s happened"),
|
||||||
EXPECT_EQ("[42] something happened",
|
|
||||||
fmt::vsprintf("[%d] %s happened",
|
|
||||||
{fmt::make_printf_args(42, "something")}));
|
{fmt::make_printf_args(42, "something")}));
|
||||||
}
|
|
||||||
|
|
||||||
TEST(printf_test, vsprintf_make_wargs_example) {
|
|
||||||
fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
|
|
||||||
42, L"something"};
|
|
||||||
fmt::basic_format_args<fmt::wprintf_context> args(as);
|
|
||||||
EXPECT_EQ(L"[42] something happened",
|
EXPECT_EQ(L"[42] something happened",
|
||||||
fmt::vsprintf(L"[%d] %s happened", args));
|
fmt::vsprintf(fmt::basic_string_view<wchar_t>(L"[%d] %s happened"),
|
||||||
auto as2 = fmt::make_wprintf_args(42, L"something");
|
|
||||||
fmt::basic_format_args<fmt::wprintf_context> args2(as2);
|
|
||||||
EXPECT_EQ(L"[42] something happened",
|
|
||||||
fmt::vsprintf(L"[%d] %s happened", args2));
|
|
||||||
EXPECT_EQ(L"[42] something happened",
|
|
||||||
fmt::vsprintf(L"[%d] %s happened",
|
|
||||||
{fmt::make_wprintf_args(42, L"something")}));
|
{fmt::make_wprintf_args(42, L"something")}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
#include "fmt/ranges.h"
|
#include "fmt/ranges.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <queue>
|
||||||
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
@ -40,6 +43,8 @@ TEST(ranges_test, format_2d_array) {
|
||||||
TEST(ranges_test, format_array_of_literals) {
|
TEST(ranges_test, format_array_of_literals) {
|
||||||
const char* arr[] = {"1234", "abcd"};
|
const char* arr[] = {"1234", "abcd"};
|
||||||
EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
|
EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
|
||||||
|
EXPECT_EQ(fmt::format("{:n}", arr), "\"1234\", \"abcd\"");
|
||||||
|
EXPECT_EQ(fmt::format("{:n:}", arr), "1234, abcd");
|
||||||
}
|
}
|
||||||
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
|
|
||||||
|
@ -47,17 +52,28 @@ TEST(ranges_test, format_vector) {
|
||||||
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
||||||
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
||||||
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
|
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
|
||||||
|
EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
|
||||||
|
|
||||||
|
auto vc = std::vector<char>{'a', 'b', 'c'};
|
||||||
|
auto vvc = std::vector<std::vector<char>>{vc, vc};
|
||||||
|
EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']");
|
||||||
|
EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]");
|
||||||
|
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
|
||||||
|
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
|
||||||
|
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, format_vector2) {
|
TEST(ranges_test, format_vector2) {
|
||||||
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
|
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
|
||||||
EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
|
EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
|
||||||
EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]");
|
EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]");
|
||||||
|
EXPECT_EQ(fmt::format("{:n:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, format_map) {
|
TEST(ranges_test, format_map) {
|
||||||
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
|
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
|
||||||
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
|
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
|
||||||
|
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, format_set) {
|
TEST(ranges_test, format_set) {
|
||||||
|
@ -65,6 +81,35 @@ TEST(ranges_test, format_set) {
|
||||||
"{\"one\", \"two\"}");
|
"{\"one\", \"two\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Models std::flat_set close enough to test if no ambiguous lookup of a
|
||||||
|
// formatter happens due to the flat_set type matching is_set and
|
||||||
|
// is_container_adaptor_like
|
||||||
|
template <typename T> class flat_set {
|
||||||
|
public:
|
||||||
|
using key_type = T;
|
||||||
|
using container_type = std::vector<T>;
|
||||||
|
|
||||||
|
using iterator = typename std::vector<T>::iterator;
|
||||||
|
using const_iterator = typename std::vector<T>::const_iterator;
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
explicit flat_set(Ts&&... args) : c{std::forward<Ts>(args)...} {}
|
||||||
|
|
||||||
|
iterator begin() { return c.begin(); }
|
||||||
|
const_iterator begin() const { return c.begin(); }
|
||||||
|
|
||||||
|
iterator end() { return c.end(); }
|
||||||
|
const_iterator end() const { return c.end(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<T> c;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ranges_test, format_flat_set) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
|
||||||
|
"{\"one\", \"two\"}");
|
||||||
|
}
|
||||||
|
|
||||||
namespace adl {
|
namespace adl {
|
||||||
struct box {
|
struct box {
|
||||||
int value;
|
int value;
|
||||||
|
@ -91,6 +136,7 @@ TEST(ranges_test, format_tuple) {
|
||||||
auto t =
|
auto t =
|
||||||
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
|
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
|
||||||
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
|
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
|
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
|
||||||
|
|
||||||
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
|
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
|
||||||
|
@ -103,6 +149,25 @@ TEST(ranges_test, format_tuple) {
|
||||||
EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
|
EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct not_default_formattable {};
|
||||||
|
struct bad_format {};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<not_default_formattable> {
|
||||||
|
auto parse(format_parse_context&) -> const char* { throw bad_format(); }
|
||||||
|
auto format(not_default_formattable, format_context& ctx)
|
||||||
|
-> format_context::iterator {
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(ranges_test, tuple_parse_calls_element_parse) {
|
||||||
|
auto f = fmt::formatter<std::tuple<not_default_formattable>>();
|
||||||
|
auto ctx = fmt::format_parse_context("");
|
||||||
|
EXPECT_THROW(f.parse(ctx), bad_format);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
||||||
struct tuple_like {
|
struct tuple_like {
|
||||||
int i;
|
int i;
|
||||||
|
@ -144,32 +209,24 @@ TEST(ranges_test, format_to) {
|
||||||
EXPECT_STREQ(buf, "[1, 2, 3]");
|
EXPECT_STREQ(buf, "[1, 2, 3]");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct path_like {
|
template <typename Char> struct path_like {
|
||||||
const path_like* begin() const;
|
const path_like* begin() const;
|
||||||
const path_like* end() const;
|
const path_like* end() const;
|
||||||
|
|
||||||
operator std::string() const;
|
operator std::basic_string<Char>() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(ranges_test, path_like) {
|
TEST(ranges_test, disabled_range_formatting_of_path) {
|
||||||
EXPECT_FALSE((fmt::is_range<path_like, char>::value));
|
// Range formatting of path is disabled because of infinite recursion
|
||||||
|
// (path element is itself a path).
|
||||||
|
EXPECT_EQ((fmt::range_format_kind<path_like<char>, char>::value),
|
||||||
|
fmt::range_format::disabled);
|
||||||
|
EXPECT_EQ((fmt::range_format_kind<path_like<wchar_t>, char>::value),
|
||||||
|
fmt::range_format::disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FMT_USE_STRING_VIEW
|
// A range that provides non-const only begin()/end() to test fmt::join
|
||||||
struct string_like {
|
// handles that.
|
||||||
const char* begin();
|
|
||||||
const char* end();
|
|
||||||
operator fmt::string_view() const { return "foo"; }
|
|
||||||
operator std::string_view() const { return "foo"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(ranges_test, format_string_like) {
|
|
||||||
EXPECT_EQ(fmt::format("{}", string_like()), "foo");
|
|
||||||
}
|
|
||||||
#endif // FMT_USE_STRING_VIEW
|
|
||||||
|
|
||||||
// A range that provides non-const only begin()/end() to test fmt::join handles
|
|
||||||
// that.
|
|
||||||
//
|
//
|
||||||
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
|
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
|
||||||
// information during iteration so they only provide non-const begin()/end().
|
// information during iteration so they only provide non-const begin()/end().
|
||||||
|
@ -193,7 +250,7 @@ template <typename T> class noncopyable_range {
|
||||||
std::vector<T> vec;
|
std::vector<T> vec;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using const_iterator = typename ::std::vector<T>::const_iterator;
|
using iterator = typename ::std::vector<T>::iterator;
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
explicit noncopyable_range(Args&&... args)
|
explicit noncopyable_range(Args&&... args)
|
||||||
|
@ -202,8 +259,8 @@ template <typename T> class noncopyable_range {
|
||||||
noncopyable_range(noncopyable_range const&) = delete;
|
noncopyable_range(noncopyable_range const&) = delete;
|
||||||
noncopyable_range(noncopyable_range&) = delete;
|
noncopyable_range(noncopyable_range&) = delete;
|
||||||
|
|
||||||
const_iterator begin() const { return vec.begin(); }
|
iterator begin() { return vec.begin(); }
|
||||||
const_iterator end() const { return vec.end(); }
|
iterator end() { return vec.end(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(ranges_test, range) {
|
TEST(ranges_test, range) {
|
||||||
|
@ -232,7 +289,6 @@ TEST(ranges_test, enum_range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !FMT_MSC_VERSION
|
#if !FMT_MSC_VERSION
|
||||||
|
|
||||||
TEST(ranges_test, unformattable_range) {
|
TEST(ranges_test, unformattable_range) {
|
||||||
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
|
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
|
||||||
fmt::format_context>::value));
|
fmt::format_context>::value));
|
||||||
|
@ -364,7 +420,7 @@ TEST(ranges_test, is_printable) {
|
||||||
EXPECT_FALSE(is_printable(0x110000));
|
EXPECT_FALSE(is_printable(0x110000));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ranges_test, escape_string) {
|
TEST(ranges_test, escape) {
|
||||||
using vec = std::vector<std::string>;
|
using vec = std::vector<std::string>;
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
|
||||||
|
@ -375,22 +431,22 @@ TEST(ranges_test, escape_string) {
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
|
||||||
// Unassigned Unicode code points.
|
// Unassigned Unicode code points.
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
|
||||||
|
// Broken utf-8.
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
|
EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
|
||||||
"[\"\\xf4\\x8f\\xbf\\xc0\"]");
|
"[\"\\xf4\\x8f\\xbf\\xc0\"]");
|
||||||
EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]");
|
EXPECT_EQ(fmt::format("{}", vec{"\xf0\x28"}), "[\"\\xf0(\"]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", vec{"\xe1\x28"}), "[\"\\xe1(\"]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", vec{std::string("\xf0\x28\0\0anything", 12)}),
|
||||||
|
"[\"\\xf0(\\x00\\x00anything\"]");
|
||||||
|
|
||||||
|
// Correct utf-8.
|
||||||
|
EXPECT_EQ(fmt::format("{}", vec{"🦄"}), "[\"🦄\"]");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef FMT_USE_STRING_VIEW
|
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
|
||||||
struct convertible_to_string_view {
|
"[['x']]");
|
||||||
operator std::string_view() const { return "foo"; }
|
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
|
||||||
};
|
|
||||||
|
|
||||||
TEST(ranges_test, escape_convertible_to_string_view) {
|
|
||||||
EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)),
|
|
||||||
"[\"foo\"]");
|
|
||||||
}
|
}
|
||||||
#endif // FMT_USE_STRING_VIEW
|
|
||||||
|
|
||||||
template <typename R> struct fmt_ref_view {
|
template <typename R> struct fmt_ref_view {
|
||||||
R* r;
|
R* r;
|
||||||
|
@ -406,3 +462,66 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
|
||||||
fmt_ref_view<decltype(v)> r{&v};
|
fmt_ref_view<decltype(v)> r{&v};
|
||||||
EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]");
|
EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ranges_test, vector_char) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ranges_test, container_adaptor) {
|
||||||
|
{
|
||||||
|
using fmt::detail::is_container_adaptor_like;
|
||||||
|
using T = std::nullptr_t;
|
||||||
|
static_assert(is_container_adaptor_like<std::stack<T>>::value, "");
|
||||||
|
static_assert(is_container_adaptor_like<std::queue<T>>::value, "");
|
||||||
|
static_assert(is_container_adaptor_like<std::priority_queue<T>>::value, "");
|
||||||
|
static_assert(!is_container_adaptor_like<std::vector<T>>::value, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto s = std::stack<int>();
|
||||||
|
s.push(1);
|
||||||
|
s.push(2);
|
||||||
|
EXPECT_EQ(fmt::format("{}", s), "[1, 2]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto q = std::queue<int>();
|
||||||
|
q.push(1);
|
||||||
|
q.push(2);
|
||||||
|
EXPECT_EQ(fmt::format("{}", q), "[1, 2]");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto q = std::priority_queue<int>();
|
||||||
|
q.push(3);
|
||||||
|
q.push(1);
|
||||||
|
q.push(2);
|
||||||
|
q.push(4);
|
||||||
|
EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto s = std::stack<char, std::string>();
|
||||||
|
s.push('a');
|
||||||
|
s.push('b');
|
||||||
|
// See https://cplusplus.github.io/LWG/issue3881.
|
||||||
|
EXPECT_EQ(fmt::format("{}", s), "['a', 'b']");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct my_container_adaptor {
|
||||||
|
using value_type = int;
|
||||||
|
using container_type = std::vector<value_type>;
|
||||||
|
void push(const value_type& v) { c.push_back(v); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
container_type c;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto m = my_container_adaptor();
|
||||||
|
m.push(1);
|
||||||
|
m.push(2);
|
||||||
|
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ TEST(scan_test, read_string_view) {
|
||||||
EXPECT_EQ(s, "foo");
|
EXPECT_EQ(s, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifdef FMT_HAVE_STRPTIME
|
||||||
namespace fmt {
|
namespace fmt {
|
||||||
template <> struct scanner<tm> {
|
template <> struct scanner<tm> {
|
||||||
std::string format;
|
std::string format;
|
||||||
|
|
31
test/scan.h
31
test/scan.h
|
@ -42,7 +42,7 @@ struct scan_context {
|
||||||
public:
|
public:
|
||||||
using iterator = const char*;
|
using iterator = const char*;
|
||||||
|
|
||||||
explicit scan_context(string_view input) : input_(input) {}
|
explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {}
|
||||||
|
|
||||||
iterator begin() const { return input_.data(); }
|
iterator begin() const { return input_.data(); }
|
||||||
iterator end() const { return begin() + input_.size(); }
|
iterator end() const { return begin() + input_.size(); }
|
||||||
|
@ -83,17 +83,21 @@ class scan_arg {
|
||||||
// TODO: more types
|
// TODO: more types
|
||||||
};
|
};
|
||||||
|
|
||||||
scan_arg() : type(scan_type::none_type) {}
|
FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {}
|
||||||
scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {}
|
FMT_CONSTEXPR scan_arg(int& value)
|
||||||
scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {}
|
: type(scan_type::int_type), int_value(&value) {}
|
||||||
scan_arg(long long& value)
|
FMT_CONSTEXPR scan_arg(unsigned& value)
|
||||||
|
: type(scan_type::uint_type), uint_value(&value) {}
|
||||||
|
FMT_CONSTEXPR scan_arg(long long& value)
|
||||||
: type(scan_type::long_long_type), long_long_value(&value) {}
|
: type(scan_type::long_long_type), long_long_value(&value) {}
|
||||||
scan_arg(unsigned long long& value)
|
FMT_CONSTEXPR scan_arg(unsigned long long& value)
|
||||||
: type(scan_type::ulong_long_type), ulong_long_value(&value) {}
|
: type(scan_type::ulong_long_type), ulong_long_value(&value) {}
|
||||||
scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {}
|
FMT_CONSTEXPR scan_arg(std::string& value)
|
||||||
scan_arg(fmt::string_view& value)
|
: type(scan_type::string_type), string(&value) {}
|
||||||
|
FMT_CONSTEXPR scan_arg(fmt::string_view& value)
|
||||||
: type(scan_type::string_view_type), string_view(&value) {}
|
: type(scan_type::string_view_type), string_view(&value) {}
|
||||||
template <typename T> scan_arg(T& value) : type(scan_type::custom_type) {
|
template <typename T>
|
||||||
|
FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) {
|
||||||
custom.value = &value;
|
custom.value = &value;
|
||||||
custom.scan = scan_custom_arg<T>;
|
custom.scan = scan_custom_arg<T>;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +118,7 @@ struct scan_args {
|
||||||
const detail::scan_arg* data;
|
const detail::scan_arg* data;
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
scan_args(const std::array<detail::scan_arg, N>& store)
|
FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store)
|
||||||
: size(N), data(store.data()) {
|
: size(N), data(store.data()) {
|
||||||
static_assert(N < INT_MAX, "too many arguments");
|
static_assert(N < INT_MAX, "too many arguments");
|
||||||
}
|
}
|
||||||
|
@ -154,7 +158,8 @@ struct scan_handler : error_handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
scan_handler(string_view format, string_view input, scan_args args)
|
FMT_CONSTEXPR scan_handler(string_view format, string_view input,
|
||||||
|
scan_args args)
|
||||||
: parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
|
: parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
|
||||||
|
|
||||||
const char* pos() const { return scan_ctx_.begin(); }
|
const char* pos() const { return scan_ctx_.begin(); }
|
||||||
|
@ -162,10 +167,8 @@ struct scan_handler : error_handler {
|
||||||
void on_text(const char* begin, const char* end) {
|
void on_text(const char* begin, const char* end) {
|
||||||
auto size = to_unsigned(end - begin);
|
auto size = to_unsigned(end - begin);
|
||||||
auto it = scan_ctx_.begin();
|
auto it = scan_ctx_.begin();
|
||||||
if (it + size > scan_ctx_.end() ||
|
if (it + size > scan_ctx_.end() || !std::equal(begin, end, it))
|
||||||
!std::equal(begin, end, make_checked(it, size))) {
|
|
||||||
on_error("invalid input");
|
on_error("invalid input");
|
||||||
}
|
|
||||||
scan_ctx_.advance_to(it + size);
|
scan_ctx_.advance_to(it + size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.1...3.18)
|
cmake_minimum_required(VERSION 3.8...3.25)
|
||||||
|
|
||||||
project(fmt-link CXX)
|
project(fmt-link CXX)
|
||||||
|
|
||||||
|
|
223
test/std-test.cc
223
test/std-test.cc
|
@ -6,52 +6,110 @@
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#include "fmt/std.h"
|
#include "fmt/std.h"
|
||||||
#include "fmt/ranges.h"
|
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "fmt/os.h" // fmt::system_category
|
||||||
|
#include "fmt/ranges.h"
|
||||||
|
#include "gtest-extra.h" // StartsWith
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_filesystem
|
||||||
TEST(std_test, path) {
|
TEST(std_test, path) {
|
||||||
// Test ambiguity problem described in #2954. We need to exclude compilers
|
using std::filesystem::path;
|
||||||
// where the ambiguity problem cannot be solved for now.
|
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
|
||||||
#if defined(__cpp_lib_filesystem) && \
|
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
|
||||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920)
|
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
|
||||||
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
|
|
||||||
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")),
|
EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar");
|
||||||
"\"foo\\\"bar.txt\"");
|
EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\"");
|
||||||
|
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
// File.txt in Russian.
|
EXPECT_EQ(fmt::format("{}", path(
|
||||||
const wchar_t unicode_path[] = {0x424, 0x430, 0x439, 0x43b, 0x2e,
|
L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
|
||||||
0x74, 0x78, 0x74, 0};
|
L"\x0447\x044B\x043D\x0430")),
|
||||||
const char unicode_u8path[] = {'"', char(0xd0), char(0xa4), char(0xd0),
|
"Шчучыншчына");
|
||||||
char(0xb0), char(0xd0), char(0xb9), char(0xd0),
|
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
|
||||||
char(0xbb), '.', 't', 'x',
|
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
|
||||||
't', '"', '\0'};
|
|
||||||
EXPECT_EQ(fmt::format("{}", std::filesystem::path(unicode_path)),
|
|
||||||
unicode_u8path);
|
|
||||||
# endif
|
# endif
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test ambiguity problem described in #2954.
|
||||||
TEST(ranges_std_test, format_vector_path) {
|
TEST(ranges_std_test, format_vector_path) {
|
||||||
// Test ambiguity problem described in #2954. We need to exclude compilers
|
|
||||||
// where the ambiguity problem cannot be solved for now.
|
|
||||||
#if defined(__cpp_lib_filesystem) && \
|
|
||||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920)
|
|
||||||
auto p = std::filesystem::path("foo/bar.txt");
|
auto p = std::filesystem::path("foo/bar.txt");
|
||||||
auto c = std::vector<std::string>{"abc", "def"};
|
auto c = std::vector<std::string>{"abc", "def"};
|
||||||
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
|
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
|
||||||
"path=\"foo/bar.txt\", range=[\"abc\", \"def\"]");
|
"path=foo/bar.txt, range=[\"abc\", \"def\"]");
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that path is not escaped twice in the debug mode.
|
||||||
|
TEST(ranges_std_test, format_quote_path) {
|
||||||
|
auto vec =
|
||||||
|
std::vector<std::filesystem::path>{"path1/file1.txt", "path2/file2.txt"};
|
||||||
|
EXPECT_EQ(fmt::format("{}", vec),
|
||||||
|
"[\"path1/file1.txt\", \"path2/file2.txt\"]");
|
||||||
|
# ifdef __cpp_lib_optional
|
||||||
|
auto o = std::optional<std::filesystem::path>("path/file.txt");
|
||||||
|
EXPECT_EQ(fmt::format("{}", o), "optional(\"path/file.txt\")");
|
||||||
|
EXPECT_EQ(fmt::format("{:?}", o), "optional(\"path/file.txt\")");
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(std_test, thread_id) {
|
TEST(std_test, thread_id) {
|
||||||
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
|
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(std_test, optional) {
|
||||||
|
#ifdef __cpp_lib_optional
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2},
|
||||||
|
std::optional{3}}),
|
||||||
|
"[optional(1), optional(2), optional(3)]");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}),
|
||||||
|
"optional(optional(\"nested\"))");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30),
|
||||||
|
"optional(\"left aligned\" )");
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}),
|
||||||
|
"optional([104, 101, 108, 108, 111])");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}),
|
||||||
|
"optional(\"string\")");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')");
|
||||||
|
EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)");
|
||||||
|
|
||||||
|
struct unformattable {};
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
|
||||||
|
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
struct throws_on_move {
|
||||||
|
throws_on_move() = default;
|
||||||
|
|
||||||
|
[[noreturn]] throws_on_move(throws_on_move&&) {
|
||||||
|
throw std::runtime_error("Thrown by throws_on_move");
|
||||||
|
}
|
||||||
|
|
||||||
|
throws_on_move(const throws_on_move&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace fmt {
|
||||||
|
template <> struct formatter<throws_on_move> : formatter<string_view> {
|
||||||
|
auto format(const throws_on_move&, format_context& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
string_view str("<throws_on_move>");
|
||||||
|
return formatter<string_view>::format(str, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace fmt
|
||||||
|
|
||||||
TEST(std_test, variant) {
|
TEST(std_test, variant) {
|
||||||
#ifdef __cpp_lib_variant
|
#ifdef __cpp_lib_variant
|
||||||
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
|
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
|
||||||
|
@ -80,5 +138,120 @@ TEST(std_test, variant) {
|
||||||
|
|
||||||
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
|
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
|
||||||
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
|
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
|
||||||
|
|
||||||
|
volatile int i = 42; // Test compile error before GCC 11 described in #3068.
|
||||||
|
EXPECT_EQ(fmt::format("{}", i), "42");
|
||||||
|
|
||||||
|
std::variant<std::monostate, throws_on_move> v6;
|
||||||
|
|
||||||
|
try {
|
||||||
|
throws_on_move thrower;
|
||||||
|
v6.emplace<throws_on_move>(std::move(thrower));
|
||||||
|
} catch (const std::runtime_error&) {
|
||||||
|
}
|
||||||
|
// v6 is now valueless by exception
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", v6), "variant(valueless by exception)");
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(std_test, error_code) {
|
||||||
|
EXPECT_EQ("generic:42",
|
||||||
|
fmt::format(FMT_STRING("{0}"),
|
||||||
|
std::error_code(42, std::generic_category())));
|
||||||
|
EXPECT_EQ("system:42",
|
||||||
|
fmt::format(FMT_STRING("{0}"),
|
||||||
|
std::error_code(42, fmt::system_category())));
|
||||||
|
EXPECT_EQ("system:-42",
|
||||||
|
fmt::format(FMT_STRING("{0}"),
|
||||||
|
std::error_code(-42, fmt::system_category())));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Catch> void exception_test() {
|
||||||
|
try {
|
||||||
|
throw std::runtime_error("Test Exception");
|
||||||
|
} catch (const Catch& ex) {
|
||||||
|
EXPECT_EQ("Test Exception", fmt::format("{}", ex));
|
||||||
|
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace my_ns1 {
|
||||||
|
namespace my_ns2 {
|
||||||
|
struct my_exception : public std::exception {
|
||||||
|
private:
|
||||||
|
std::string msg;
|
||||||
|
|
||||||
|
public:
|
||||||
|
my_exception(const std::string& s) : msg(s) {}
|
||||||
|
const char* what() const noexcept override;
|
||||||
|
};
|
||||||
|
const char* my_exception::what() const noexcept { return msg.c_str(); }
|
||||||
|
} // namespace my_ns2
|
||||||
|
} // namespace my_ns1
|
||||||
|
|
||||||
|
TEST(std_test, exception) {
|
||||||
|
using testing::StartsWith;
|
||||||
|
exception_test<std::exception>();
|
||||||
|
exception_test<std::runtime_error>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
using namespace my_ns1::my_ns2;
|
||||||
|
throw my_exception("My Exception");
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception",
|
||||||
|
fmt::format("{:t}", ex));
|
||||||
|
EXPECT_EQ("My Exception", fmt::format("{:}", ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw std::system_error(std::error_code(), "message");
|
||||||
|
} catch (const std::system_error& ex) {
|
||||||
|
EXPECT_THAT(fmt::format("{:t}", ex), StartsWith("std::system_error: "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_filesystem
|
||||||
|
// Tests that the inline namespace is stripped out, e.g.
|
||||||
|
// std::filesystem::__cxx11::* -> std::filesystem::*.
|
||||||
|
try {
|
||||||
|
throw std::filesystem::filesystem_error("message", std::error_code());
|
||||||
|
} catch (const std::filesystem::filesystem_error& ex) {
|
||||||
|
EXPECT_THAT(fmt::format("{:t}", ex),
|
||||||
|
StartsWith("std::filesystem::filesystem_error: "));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(std_test, format_bit_reference) {
|
||||||
|
std::bitset<2> bs(1);
|
||||||
|
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
|
||||||
|
std::vector<bool> v = {true, false};
|
||||||
|
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(std_test, format_const_bit_reference) {
|
||||||
|
const std::bitset<2> bs(1);
|
||||||
|
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
|
||||||
|
const std::vector<bool> v = {true, false};
|
||||||
|
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(std_test, format_atomic) {
|
||||||
|
std::atomic<bool> b(false);
|
||||||
|
EXPECT_EQ(fmt::format("{}", b), "false");
|
||||||
|
|
||||||
|
const std::atomic<bool> cb(true);
|
||||||
|
EXPECT_EQ(fmt::format("{}", cb), "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_atomic_flag_test
|
||||||
|
TEST(std_test, format_atomic_flag) {
|
||||||
|
std::atomic_flag f = ATOMIC_FLAG_INIT;
|
||||||
|
(void) f.test_and_set();
|
||||||
|
EXPECT_EQ(fmt::format("{}", f), "true");
|
||||||
|
|
||||||
|
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
|
||||||
|
EXPECT_EQ(fmt::format("{}", cf), "false");
|
||||||
|
}
|
||||||
|
#endif // __cpp_lib_atomic_flag_test
|
||||||
|
|
|
@ -18,12 +18,12 @@ using testing::Contains;
|
||||||
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
|
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
|
||||||
|
|
||||||
TEST(unicode_test, legacy_locale) {
|
TEST(unicode_test, legacy_locale) {
|
||||||
auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251");
|
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");
|
||||||
if (loc == std::locale::classic()) return;
|
if (loc == std::locale::classic()) return;
|
||||||
|
|
||||||
auto s = std::string();
|
auto s = std::string();
|
||||||
try {
|
try {
|
||||||
s = fmt::format(loc, "День недели: {:L}", fmt::weekday(1));
|
s = fmt::format(loc, "Дзень тыдня: {:L}", fmt::weekday(1));
|
||||||
} catch (const fmt::format_error& e) {
|
} catch (const fmt::format_error& e) {
|
||||||
// Formatting can fail due to an unsupported encoding.
|
// Formatting can fail due to an unsupported encoding.
|
||||||
fmt::print("Format error: {}\n", e.what());
|
fmt::print("Format error: {}\n", e.what());
|
||||||
|
@ -38,11 +38,11 @@ TEST(unicode_test, legacy_locale) {
|
||||||
os << std::put_time(&tm, "%a");
|
os << std::put_time(&tm, "%a");
|
||||||
auto wd = os.str();
|
auto wd = os.str();
|
||||||
if (wd == "??") {
|
if (wd == "??") {
|
||||||
EXPECT_EQ(s, "День недели: ??");
|
EXPECT_EQ(s, "Дзень тыдня: ??");
|
||||||
fmt::print("std::locale gives ?? as a weekday.\n");
|
fmt::print("std::locale gives ?? as a weekday.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
EXPECT_THAT((std::vector<std::string>{"День недели: пн", "День недели: Пн"}),
|
EXPECT_THAT((std::vector<std::string>{"Дзень тыдня: пн", "Дзень тыдня: Пан"}),
|
||||||
Contains(s));
|
Contains(s));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,7 @@
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifdef FMT_MODULE_TEST
|
#include "fmt/os.h"
|
||||||
import fmt;
|
|
||||||
#else
|
|
||||||
# include "fmt/os.h"
|
|
||||||
#endif // FMT_MODULE_TEST
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define FMT_VSNPRINTF vsprintf_s
|
# define FMT_VSNPRINTF vsprintf_s
|
||||||
|
@ -81,5 +77,5 @@ class date {
|
||||||
int day() const { return day_; }
|
int day() const { return day_; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a locale with the given name if available or classic locale othewise.
|
// Returns a locale with the given name if available or classic locale otherwise.
|
||||||
std::locale get_locale(const char* name, const char* alt_name = nullptr);
|
std::locale get_locale(const char* name, const char* alt_name = nullptr);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "fmt/xchar.h"
|
#include "fmt/xchar.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <complex>
|
#include <complex>
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -15,31 +16,21 @@
|
||||||
#include "fmt/color.h"
|
#include "fmt/color.h"
|
||||||
#include "fmt/ostream.h"
|
#include "fmt/ostream.h"
|
||||||
#include "fmt/ranges.h"
|
#include "fmt/ranges.h"
|
||||||
|
#include "fmt/std.h"
|
||||||
#include "gtest-extra.h" // Contains
|
#include "gtest-extra.h" // Contains
|
||||||
#include "util.h" // get_locale
|
#include "util.h" // get_locale
|
||||||
|
|
||||||
using fmt::detail::max_value;
|
using fmt::detail::max_value;
|
||||||
using testing::Contains;
|
using testing::Contains;
|
||||||
|
|
||||||
namespace test_ns {
|
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||||
template <typename Char> class test_string {
|
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||||
private:
|
# define FMT_HAS_C99_STRFTIME 0
|
||||||
std::basic_string<Char> s_;
|
#else
|
||||||
|
# define FMT_HAS_C99_STRFTIME 1
|
||||||
public:
|
#endif
|
||||||
test_string(const Char* s) : s_(s) {}
|
|
||||||
const Char* data() const { return s_.data(); }
|
|
||||||
size_t length() const { return s_.size(); }
|
|
||||||
operator const Char*() const { return s_.c_str(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
|
|
||||||
return {s.data(), s.length()};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct non_string {};
|
struct non_string {};
|
||||||
} // namespace test_ns
|
|
||||||
|
|
||||||
template <typename T> class is_string_test : public testing::Test {};
|
template <typename T> class is_string_test : public testing::Test {};
|
||||||
|
|
||||||
|
@ -61,8 +52,7 @@ TYPED_TEST(is_string_test, is_string) {
|
||||||
using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
|
using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
|
||||||
EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
|
EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
|
||||||
fmt::detail::is_string<fmt_string_view>::value);
|
fmt::detail::is_string<fmt_string_view>::value);
|
||||||
EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value);
|
EXPECT_FALSE(fmt::detail::is_string<non_string>::value);
|
||||||
EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// std::is_constructible is broken in MSVC until version 2015.
|
// std::is_constructible is broken in MSVC until version 2015.
|
||||||
|
@ -84,7 +74,7 @@ TEST(xchar_test, format) {
|
||||||
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
|
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
|
||||||
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
|
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
|
||||||
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
|
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
|
||||||
EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
|
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
|
||||||
EXPECT_EQ(L"true", fmt::format(L"{}", true));
|
EXPECT_EQ(L"true", fmt::format(L"{}", true));
|
||||||
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
|
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
|
||||||
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
|
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
|
||||||
|
@ -98,8 +88,9 @@ TEST(xchar_test, is_formattable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, compile_time_string) {
|
TEST(xchar_test, compile_time_string) {
|
||||||
|
EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42");
|
||||||
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
||||||
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
|
EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +102,12 @@ struct custom_char {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
|
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
|
||||||
|
|
||||||
operator int() const { return value; }
|
operator char() const {
|
||||||
|
return value <= 0xff ? static_cast<char>(value) : '\0';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int to_ascii(custom_char c) { return c; }
|
auto to_ascii(custom_char c) -> char { return c; }
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct is_char<custom_char> : std::true_type {};
|
template <> struct is_char<custom_char> : std::true_type {};
|
||||||
|
@ -153,17 +146,21 @@ TEST(xchar_test, format_to) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, vformat_to) {
|
TEST(xchar_test, vformat_to) {
|
||||||
using wcontext = fmt::wformat_context;
|
auto args = fmt::make_wformat_args(42);
|
||||||
fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42);
|
|
||||||
auto wargs = fmt::basic_format_args<wcontext>(&warg, 1);
|
|
||||||
auto w = std::wstring();
|
auto w = std::wstring();
|
||||||
fmt::vformat_to(std::back_inserter(w), L"{}", wargs);
|
fmt::vformat_to(std::back_inserter(w), L"{}", args);
|
||||||
EXPECT_EQ(L"42", w);
|
|
||||||
w.clear();
|
|
||||||
fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs);
|
|
||||||
EXPECT_EQ(L"42", w);
|
EXPECT_EQ(L"42", w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
struct struct_as_wstring_view {};
|
||||||
|
auto format_as(struct_as_wstring_view) -> fmt::wstring_view { return L"foo"; }
|
||||||
|
} // namespace test
|
||||||
|
|
||||||
|
TEST(xchar_test, format_as) {
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", test::struct_as_wstring_view()), L"foo");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(format_test, wide_format_to_n) {
|
TEST(format_test, wide_format_to_n) {
|
||||||
wchar_t buffer[4];
|
wchar_t buffer[4];
|
||||||
buffer[3] = L'x';
|
buffer[3] = L'x';
|
||||||
|
@ -199,7 +196,10 @@ TEST(xchar_test, named_arg_udl) {
|
||||||
|
|
||||||
TEST(xchar_test, print) {
|
TEST(xchar_test, print) {
|
||||||
// Check that the wide print overload compiles.
|
// Check that the wide print overload compiles.
|
||||||
if (fmt::detail::const_check(false)) fmt::print(L"test");
|
if (fmt::detail::const_check(false)) {
|
||||||
|
fmt::print(L"test");
|
||||||
|
fmt::println(L"test");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, join) {
|
TEST(xchar_test, join) {
|
||||||
|
@ -229,11 +229,24 @@ TEST(xchar_test, enum) {
|
||||||
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
|
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct streamable_and_unformattable {};
|
||||||
|
|
||||||
|
auto operator<<(std::wostream& os, streamable_and_unformattable)
|
||||||
|
-> std::wostream& {
|
||||||
|
return os << L"foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(xchar_test, streamed) {
|
||||||
|
EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>());
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())),
|
||||||
|
L"foo");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(xchar_test, sign_not_truncated) {
|
TEST(xchar_test, sign_not_truncated) {
|
||||||
wchar_t format_str[] = {
|
wchar_t format_str[] = {
|
||||||
L'{', L':',
|
L'{', L':',
|
||||||
'+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
|
'+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
|
||||||
EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error);
|
EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(xchar_test, chrono) {
|
TEST(xchar_test, chrono) {
|
||||||
|
@ -269,8 +282,9 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(chrono_test, time_point) {
|
TEST(chrono_test_wchar, time_point) {
|
||||||
auto t1 = std::chrono::system_clock::now();
|
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
|
||||||
std::vector<std::wstring> spec_list = {
|
std::vector<std::wstring> spec_list = {
|
||||||
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
|
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
|
||||||
|
@ -278,14 +292,36 @@ TEST(chrono_test, time_point) {
|
||||||
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
|
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
|
||||||
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
|
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
|
||||||
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
|
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
|
||||||
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
|
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
|
||||||
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
// Disabled on Windows, because these formats is not consistent among
|
// Disabled on Windows, because these formats is not consistent among
|
||||||
// platforms.
|
// platforms.
|
||||||
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
|
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
|
||||||
|
#elif !FMT_HAS_C99_STRFTIME
|
||||||
|
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||||
|
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
|
||||||
|
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
|
||||||
|
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
|
||||||
#endif
|
#endif
|
||||||
|
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
for (const auto& spec : spec_list) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
|
||||||
|
auto sys_output = system_wcsftime(spec, &tm);
|
||||||
|
|
||||||
|
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timezone formatters tests makes sense for localtime.
|
||||||
|
#if FMT_HAS_C99_STRFTIME
|
||||||
|
spec_list = {L"%z", L"%Z"};
|
||||||
|
#else
|
||||||
|
spec_list = {L"%Z"};
|
||||||
|
#endif
|
||||||
for (const auto& spec : spec_list) {
|
for (const auto& spec : spec_list) {
|
||||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
auto tm = *std::localtime(&t);
|
auto tm = *std::localtime(&t);
|
||||||
|
@ -293,8 +329,38 @@ TEST(chrono_test, time_point) {
|
||||||
auto sys_output = system_wcsftime(spec, &tm);
|
auto sys_output = system_wcsftime(spec, &tm);
|
||||||
|
|
||||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1));
|
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||||
EXPECT_EQ(sys_output, fmt::format(fmt_spec, tm));
|
|
||||||
|
if (spec == L"%z") {
|
||||||
|
sys_output.insert(sys_output.end() - 2, 1, L':');
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
|
||||||
|
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
||||||
|
// the timezone in std::tm (if it presents on platform).
|
||||||
|
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
|
||||||
|
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
|
||||||
|
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
|
||||||
|
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||||
|
auto tm = *std::gmtime(&t);
|
||||||
|
|
||||||
|
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
|
||||||
|
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
|
||||||
|
|
||||||
|
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
|
||||||
|
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
|
||||||
|
|
||||||
|
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
|
||||||
|
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,9 +371,17 @@ TEST(xchar_test, color) {
|
||||||
|
|
||||||
TEST(xchar_test, ostream) {
|
TEST(xchar_test, ostream) {
|
||||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
|
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
|
||||||
std::wostringstream wos;
|
{
|
||||||
fmt::print(wos, L"Don't {}!", L"panic");
|
std::wostringstream wos;
|
||||||
EXPECT_EQ(wos.str(), L"Don't panic!");
|
fmt::print(wos, L"Don't {}!", L"panic");
|
||||||
|
EXPECT_EQ(wos.str(), L"Don't panic!");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::wostringstream wos;
|
||||||
|
fmt::println(wos, L"Don't {}!", L"panic");
|
||||||
|
EXPECT_EQ(wos.str(), L"Don't panic!\n");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,6 +399,7 @@ TEST(xchar_test, escape_string) {
|
||||||
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
|
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|
||||||
template <typename Char> struct numpunct : std::numpunct<Char> {
|
template <typename Char> struct numpunct : std::numpunct<Char> {
|
||||||
protected:
|
protected:
|
||||||
Char do_decimal_point() const override { return '?'; }
|
Char do_decimal_point() const override { return '?'; }
|
||||||
|
@ -360,6 +435,9 @@ TEST(locale_test, localized_double) {
|
||||||
EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
|
EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
|
||||||
EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
|
EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
|
||||||
EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230");
|
EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230");
|
||||||
|
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 0.1), " 0?100000");
|
||||||
|
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1.0), " 1?000000");
|
||||||
|
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1e3), " 1~000?000000");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(locale_test, format) {
|
TEST(locale_test, format) {
|
||||||
|
@ -368,8 +446,8 @@ TEST(locale_test, format) {
|
||||||
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
|
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
|
||||||
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
|
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
|
||||||
EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
|
EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
|
||||||
fmt::format_arg_store<fmt::format_context, int> as{1234567};
|
auto n = 1234567;
|
||||||
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
|
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::make_format_args(n)));
|
||||||
auto s = std::string();
|
auto s = std::string();
|
||||||
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
|
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
|
||||||
EXPECT_EQ("1~234~567", s);
|
EXPECT_EQ("1~234~567", s);
|
||||||
|
@ -402,10 +480,9 @@ TEST(locale_test, wformat) {
|
||||||
auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
|
auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
|
||||||
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
|
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
|
||||||
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
|
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
|
||||||
using wcontext = fmt::buffer_context<wchar_t>;
|
int n = 1234567;
|
||||||
fmt::format_arg_store<wcontext, int> as{1234567};
|
|
||||||
EXPECT_EQ(L"1~234~567",
|
EXPECT_EQ(L"1~234~567",
|
||||||
fmt::vformat(loc, L"{:L}", fmt::basic_format_args<wcontext>(as)));
|
fmt::vformat(loc, L"{:L}", fmt::make_wformat_args(n)));
|
||||||
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
|
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
|
||||||
|
|
||||||
auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>());
|
auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>());
|
||||||
|
@ -422,16 +499,16 @@ TEST(locale_test, wformat) {
|
||||||
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
|
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(locale_test, double_formatter) {
|
TEST(locale_test, int_formatter) {
|
||||||
auto loc = std::locale(std::locale(), new special_grouping<char>());
|
auto loc = std::locale(std::locale(), new special_grouping<char>());
|
||||||
auto f = fmt::formatter<int>();
|
auto f = fmt::formatter<int>();
|
||||||
auto parse_ctx = fmt::format_parse_context("L");
|
auto parse_ctx = fmt::format_parse_context("L");
|
||||||
f.parse(parse_ctx);
|
f.parse(parse_ctx);
|
||||||
char buf[10] = {};
|
auto buf = fmt::memory_buffer();
|
||||||
fmt::basic_format_context<char*, char> format_ctx(
|
fmt::basic_format_context<fmt::appender, char> format_ctx(
|
||||||
buf, {}, fmt::detail::locale_ref(loc));
|
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
|
||||||
*f.format(12345, format_ctx) = 0;
|
f.format(12345, format_ctx);
|
||||||
EXPECT_STREQ("12,345", buf);
|
EXPECT_EQ(fmt::to_string(buf), "12,345");
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
@ -442,13 +519,10 @@ template <class charT> struct formatter<std::complex<double>, charT> {
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
|
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
|
||||||
basic_format_parse_context<charT>& ctx) {
|
basic_format_parse_context<charT>& ctx) {
|
||||||
using handler_type =
|
auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
|
||||||
detail::dynamic_specs_handler<basic_format_parse_context<charT>>;
|
detail::type::float_type);
|
||||||
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
|
detail::parse_float_type_spec(specs_, detail::error_handler());
|
||||||
detail::type::string_type);
|
return end;
|
||||||
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
|
|
||||||
detail::parse_float_type_spec(specs_, ctx.error_handler());
|
|
||||||
return it;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class FormatContext>
|
template <class FormatContext>
|
||||||
|
@ -495,4 +569,16 @@ TEST(locale_test, chrono_weekday) {
|
||||||
std::locale::global(loc_old);
|
std::locale::global(loc_old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(locale_test, sign) {
|
||||||
|
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(std_test_xchar, optional) {
|
||||||
|
# ifdef __cpp_lib_optional
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
|
||||||
|
EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}),
|
||||||
|
L"optional(\"wide string\")");
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|
Loading…
Reference in a new issue