Squashed 'externals/fmt/' changes from cd4af11e..9e8b86fd
9e8b86fd Update version 92fec0f0 Bump version 4749cc93 Update changelog 78a0ba0a Improve conversion of paragraphs 7a39837d Use a working breathe version 55b6e92d Fix docs 69dc3a85 Fix docs 27f4cdd5 Update changelog 70d61a0a Update changelog 427b5340 Add no_value state to value e421d527 Simplify error handling in parse_nonnegative_int a59678f3 Fix chrono_test.locale c98254c3 Install locales into CI c123a728 Fix set locale error in chrono formatter 3c8fad12 Optimize parse_nonnegative_int f28cf330 adding a default format for std::chrono::time_point<std::chrono::syst… (#2345) 55010a9d Support non-`char` overloads (module) 0193e7c4 Support compile-time strings and compile-time format string compilation in module 3423d754 Remove the msvc workaround (#2351) f6b5cc9f Fix chrono_test.weekday on legacy glibc 59a298f1 Enable `enforce-checks-test` for MSVC, too 36c29482 Update docs c9fe1fa5 Remove unused flag dccddc2b Apply clang-format 0e36681b Cleanup digit count 1de80f5b Workaround lack of static constexpr in constexpr functions 2039dce7 Detect consteval d551b88a Move is_char specializations to xchar.h 16c3514d wchar-test -> xchar-test 206000a0 Workaround pathological conversion (#2343) 76ee4904 Move wchar/custom char overloads to xchar.h e77b22d6 Deprecate memory buffer overload of format_to 07039f4b Update README.rst 4678192c Remove bsr2log10 7c3d3dfa Update thousands_sep_impl signature ef826b86 Fix docs 5223f552 Remove FMT_ALWAYS_INLINE cfde93af Add FMT_STATIC_CONSTEXPR 986a5a6c Fixed join_view formatter for wchar_t 7c8b35ff fix MSVC Win32 count_digits 3eeb084e Optimize count_digits 2ac0bfe5 Improve handling of thousands separator 024741b4 CI: set up multi-thread build for all platforms f4c95f6d Improve handling of thousands separator d4fbeacc Fix docs build 0eef389d Code style e27b1ce5 Fix docs 9f8b6dac Fix wheel installation 6060bcfc Fix docs ff967346 Fix docs 1085cc21 Fix docs 11addaa1 Update docs 760ca5cc Update docs 290d3f8b Cleanup ranges API aa09e0f5 Update docs d142579e Cleanup the format API f286139d Fix "undefined reference to `fmt::v7::detail::basic_data<void>::digits'" 7b9d69b8 Add xchar.h to docs cbd861f1 Update docs faf972f0 Update docs 622d1c04 Update changelog 634c9487 Update changelog a04e3a2d Comment 87876d54 Cleanup the printf implementation d338d663 Cleanup the printf implementation 272660e7 Remove deprecated printf functions 5a95c5ae Update changelog 70e67ae0 Re-enable module testing Prepare for compilation with gcc (modules branch). ad972589 Merge branch 'master' of github.com:fmtlib/fmt ed2a6377 Workaround msvc constexpr issues 99768695 fix custom types formatting at compile-time, add test 8c1b22ba Workaround a gcc 9.1 bug (#2334) 2dba1cfa Update changelog d7ba6c3e Use qualified name-lookup in module. (#2324) bf9904ee Workaround msvc bugs 577bce90 Apply clang-format ba4c7f19 Swap parameter order to match #2327 (#2329) e9e89b35 Update ChangeLog.rst 9bb406d7 Update changelog 11a14db2 Update format_to taking a buffer and remove undocumented vformat_to overload 832ec098 Fix argument order in locale overload of vformat_to (#2327) 486a80e8 Move wchar_t overloads to xchar.h 19d45f4b Update changelog 5a2b88f6 Reduce binary size 00a39ad5 Enable `Char` types other than `char` (#2323) ff37e416 wchar.h -> xchar.h because it handles other code unit types too 0901176f arg_join -> join_view a9a90181 Move wmemory_buffer to wchar.h 4a7801c3 Update changelog 517578f8 Update changelog 85442ed0 Update changelog 6a12b13a Update changelog 1cfe3c73 Update ChangeLog.rst c0601479 Update changelog 6fe04871 Update changelog 9d67988a FMT_DEPRECATED_WCHAR -> FMT_DEPRECATED_INCLUDE_WCHAR 765b451e Update changelog 17c993c7 Fixed compilation with CMake < 3.7 (#2321) dde69373 Update changelog 272b0f36 More module tests (#2309) 126c8cb4 Export os.h API, too (#2318) 98b9ff47 Align hex floats right as default (#2317) ece4b4b3 Update changelog a70a4ae0 Ignore zero-padding for non-finite floating points (#2310) 7612f18d Update changelog b9f2c276 Update changelog 4e21baff Simplify get_units 683ef11a Update changelog ca466374 qualify make_format_args (#2315) 5a2a1856 Make buffers non-movable ee52a6dc add `fmt::print()` overload to support compiled format (#2304) 82607efb Fixed int conversion warning (#2313) 35a2c2a7 Refactor chrono formatting b955e7a6 Refactor chrono formatting 883d9595 Support alternative locale names in tests 1f308a3c Update integer presentation types documentation. 1cd9899c Add initial support for weekday formatting 069131dc Add unicode-test dd8f38fc Cleanup printf API a216f256 Remove undocumented and obsolete vprintf overload 0c092639 Add is_exotic_char trait bc13c6de Update README.rst 8ec0b9e3 Do *not* export namespace `detail` b99c2bd3 Remove deprecated `locale.h` from module interface unit c04a2439 Update changelog b099a56f Update changelog 703005c8 Deprecate locale.h 51f01786 Cleanup the format API 5d59dcf6 Remove deprecated aliases / undeprecate has_formatter c242dd40 Move cerrno include to where it is used 2216e0b7 Update changelog 1c83a49b Simplify buffer extraction 2617384d Improve buffer extraction 34b8acae More wchar_t-specific API to wchar.h 6326c189 Improve code style consistency 5c4b0c86 Add missing Allocator template argument for basic_memory_buffer in format_to 00149c0b Move detail::null to chrono where it is used c5c968cb Improve binary size 128cbdeb cmake: hide private symbols by default 18af1dc4 Fix binary size regression caused by b268f88 d1e6f0f8 Fix binary size regression caused by b268f88 5a0d99fa Add a test for the module 6e2e6b79 Restore support for `wchar_t` overloads in module 24b677d0 Improve symbol sizes 63271a51 Fix ADL issues 61b4c923 Reduce code bloat 2a2e4c58 addressing nits. be48f4d6 Avoid unwanted sign extensions from MSVC in is_utf8. 13e65293 export missed symbols 71fb1138 fix compile error on msvc preview 4 (16.10) involving lookup clash /w STL 08d22503 Remove outdated comments 56f518a9 Update signatures b7f29337 Update signatures 7483dfc6 Update signatures 95c358f7 Improve separation between code unit types 39c3c4ec Simplify the core API e9c1c415 Improve compile-time checks 21d93bfd Move generic format functions to format.h 9a92eb41 Move more wchar overloads to wchar.h 0dd91e20 Add wchar.h for wide char overloads ce14eafc Simplify format string checks 8d70c0ed Refactor the format API 813ac495 More API cleanups 4ab01fb1 Cleanup printf API d5036b11 Remove deprecated APIs 25819462 Cleanup the core API b35db4e0 Improve handling of 128-bit ints d35f1ad5 Cleanup core 8f1902c0 Move format string checks to core.h 6469b903 Silence msvc warning about an unused named parameter 7d4c92fb Update ChangeLog.rst 0763d8ca Fix Visual Studio warning 5466373a Do *not* export namespace `detail` 588bdb54 Simplify get_arg_index_by_name 54f22a3e add support for statically named arguments with FMT_STRING ea94d6d9 Prevent ambiguity in name lookup 57280762 Move specs checker to core.h ced30375 Move dynamic specs to core.h dd2bc998 Move specs to core.h 08da1adc Remove unused headers 3be0cc20 Fix handling of 128-bit ints 9648bdce add missing header d1aebdbd Inline format_to 8f0fadfa Cleanup docs 02896dab Avoid use after move (#2278) 0036a1d1 Fix issue #2274. 2a9b3146 Replace fmt::error_code to std::error_code 2165bef4 Update README.rst 48629308 Optimize format string compilation 3207a8bb Get rid of unnecessary recursion to enable inlining 6214f15a Optimize standard formatter specialization cd2c78fb Use write directly in formatter specializations 4211d865 Add a formatter specialization for std::error_code. 39f28424 Cleanup tests 84feeb0f Remove redundant comments and put common case check first 2665afb5 Cleanup add-subdirectory-test d0abe7c2 Make chrono formatting locale-independent by default 50fb0b5e Fix formatting 16f2ef91 Replace fmt::system_error with std::system_error 4b885c86 Replace windows_error with system_error 5238055f Move esoteric char type support to format.h 9ac088f3 Add fmtlog to projects 849c9f61 Move is_name_start to core 23892caf Move more parsing to core 8e6390c3 Move FMT_STRING to core 51a33713 Move parsing to core 9c3af11a Cleanup tests 9d7b53cb Remove redundant formatter specialization for byte f0095ccd Add support for ranges of types without formatters to join (#2262) 4f0eadfc Exclude fallback from is_formattable 400b953f Use [] instead of {} in ranges for consistency with Python format 38bcc04a Drop range limit and cleanup tests c738c343 Cleanup tests ed7c4320 Cleanup tests 9155e2de Cleanup tests 38127d9e Cleanup tests c9c0e507 Cleanup tests ccf4ccde Cleanup tests and format string compilation e96a92f8 Cleanup tests and format string compilation fd43e4dc gtest: fix std::is_trivially_copy_constructible for GCC 4.8 & 4.9 properly 3d51ccda gtest: remove obsolete `GTEST_LANG_CXX11` compile definition setting 833377ff gtest: add `.clang-format` file into `test/gtest` directory to prevent formatting there 53ca0cbe gtest: move GTest/GMock files to separate directory, update GTest/GMock usages 342973b3 Make wchar_t overloads usable in module Bring ''detail::find()' into scope. 355be4b1 Make FMT_COMPILE fallback on runtime without if constexpr (#2261) 0cd0fb91 C++17: std::char_traits<>::{compare,length} is constexpr - v2 d1a6e560 Keep defaulted destructors inline applies to exception classes in case of msvc only 84a36b99 Move data to functions ab7c33ed Suppress checked iterator warnings 77258f60 fix FMT_CONSTEXPR_CHAR_TRAITS check for MSVC d23e315e CI windows: add MSVC C++20 build f085c3d7 use proper check for non-type template parameters 69bdc20a Workaround missing std::system on iOS, take 2 847aac43 Follow naming conventions in tests 39818e79 Cleanup core-test 0e6f989b __THROW warning fix for e2k (#2253) 1678ed62 simplify field::format() and spec_field::format(), fix typo ca821982 use named arg with static name in compile-time API ce6e7d86 use fixed_string to create named arg class with static name for _a literal fc56af14 move fixed_string from compile.h to format.h bb006f97 Replace TYPED_TEST_CASE with TYPED_TEST_SUITE 6956b10b Fix gcc 4.8 build b4f9a058 Update gtest 8f9ddf45 Remove deprecated posix.h dacd1356 Add module interface unit d3c523e0 Export printf-related contexts from printf.h 2c25df08 Export replacement type_traits, too 553022dc Don't use std::system on iOS (#2248) 8a040d18 Cleanup core-test 064cac2b Bump version 5b2c740a Remove deprecated APIs b9ab5c88 Remove printf.h dependency on ostream.h c47f2112 Simplify data handling 54d3b171 Move more data out of basic_data 128f007b C++17: std::char_traits<>::{compare,length} is constexpr. (#2246) 841aad95 Move data out of basic_data 1d4199f4 fix udl_compiled_string with non-byte chars (e.g. wchar) (#2242) c5d4fcb1 Appending a space to guarantee non-empty strftime() result. (#2244) 62714062 Fix a warning (#2233) 52bd62c7 Create separate dllexport marking points for clang and msvc. (#2229) f4bbc54c Tag official API for module export (#2235) d8910af8 Use qualified name lookup rather than ADL. (#2239) 92601141 Ranges wide strings support (#2236) 24c97515 Try to suppress MVSC warn of narrowing (#2230) a1c6bfd7 Add a link to llvm diff 42eccac4 Fix clang warning about ignoring __declspec(dllexport) on basic_data<void> template instantitation definition (#2220) aec50434 Update README.rst 0b411454 Update README.rst 00f3d16b Update docs 99c2f7a3 Allow including fmt/core.h in the header-only mode b4415323 CI linux: add clang++-11 C++20 (with LLVM libc++) build 1dbadb65 CI linux: add clang++-11 C++20 build 09dbad47 CI linux: add missing build_type e2facffe CI linux: remove excessive clang++-9 include 273d8865 Suppress redef warning of _CRT_SECURE_NO_WARNINGS if any. (#2218) 5a8bf1f6 Workaround hexfloat inconsistency on windows (#2205) 78776ee4 Fix a conditional expression is constant warning #2210 (#2211) 266107f5 constexpr uint128_wrapper (#2215) 2e0d64cf specify size for `prefixes` static data 95da4847 Fix a link 06b3a100 Add support for time points with arbitrary durations (#2208) dac42f52 Inline fallback is_constant_evaluated 7c43f8b8 Don't use strlen at compile time (#2205) c62e4c30 Make buffer_appender default-constructible when back_insert_iterator is 0d6b70d9 Install gcc 8 15c10b0c Add speech synthesis support 308510eb "Use" `fwrite` result (workaround for `warn_unused_result`) afe23e7f Don't call fileno on NULL file in tests (#2196) b49af043 Remove noexcept from file's move assignment 14848875 Fix: fmt::ostream cannot be moved while holding buffered data #2197 (#2198) 7d8c3401 Update pull_request_template.md b966afcc Remove formattable ec5315a9 Use strlen when possible in fallback basic_string_view 4f8778ba Inline basic_format_args's ctor e2d87548 user-defined constructor f7151d38 Extra flag to prevent Intel compiler with Clang front-end warning of 'unknown attribute no_sanitize' 0fb8ef8f Inline trivial argument handling functions 1b23e25f Simplify formattability check 35c71ff5 Only use -Og with optimizations disabled 243d8beb Enable minimal optimizations in debug mode 9b34681d Work around xl compiler bug when nvcc preprocesses this file (#2190) 4dc7170d Fix C++17 builds: (#2192) 9cb347b4 Simplify argument formatters 0f85a468 add default cases (#2186) 417e1cee Stop using deprecated UDL templates f7e900e1 Simplify UDL definitions d9661c8f Mark grouping as deprecated 14a2a64d Fix handling of formattable types with to_string_view (#2181) 6ae402fd Fix handling of types with to_string_view and formatter specialization (#2180) a6408a3b Add args-test 1147782c Fix an ambiguous call to check caused by ADL (#2184) 2f3f3862 Fix harmless MSVS warning about using undefined _MANAGED symbol (#2183) d0bded59 Fix MSVC /clr builds (#2179) 8308f52c Fix dynamic_format_arg_store::push_back comment 6151d0dc Fix the comment 5a1127b7 Don't wrap named arg in cref and clarify docs b8ff3c18 optimize append (#2164) c8d8b882 fix GCC 7,8,9 warning about unused but set parameter (#2177) d2810187 Document ostream support limitation bac14ef9 Simplify integer spec checking 8f9db3fc Make ubsan happy on empty format specs (#2175) af567538 Bitpack integral prefixes cdf877d4 Workaround missed optimization opportunity eef4ba9c Optimize integer formatting without padding a1ea8a82 Unbloat my heart a457e163 Simplify integer formatter 05bc87a6 Optimize padding 605b6037 Optimize count_digits for powers of 2 85ba2716 Implement 128-bit count_digits in terms of count_digits_fallback d9835737 spec -> specs f9e0e904 Apply clang-format 60f5d244 Simplify arg_formatter 30e1302e Simplify on_format_specs 87c5cd46 Optimize parsing of argument ids 6a9016ea fix `formatted_size` with "compiled format" as argument (#2161) 6e1fc017 Move detail::truncating_iterator to fmt/compile.h e718ec3e Make truncating_iterator an output_iterator (#2158) 772aeca3 Don't include <cassert>. (#2148) (#2152) 684b5b0e Fix fallback to runtime API from compile-time API (#2143) d8b92543 use simplified `void_t` for all compilers other than gcc 4.x (#2160) 835b910e Add an is_formattable trait 57887403 Revert "Optimize handling of integer constants" (#2147) 640acba8 Print x.what() of FMT_THROW when exception is disabled (#2145) d8e1c9f1 fix `fmt::get` for some GCC versions and legacy Clang (#2144) 2797588b Optimize handling of integer constants e8eff3b8 Fix FMT_STATIC_THOUSANDS_SEPARATOR (#2142) ab0f7d7f use const& for arguments 29cc8282 update chrono duration formatter (constness), use it in compile-test for specs checks 3f69af3a update wording in the error inside `arg_id_handler`, use `FMT_ASSERT` instead of `throw` 499047e1 fix incorrect indexing mode for named args, update tests 78c67157 prepare tests, fix incorrect handling of named args with simple `{}` replacement fields b31bc2dc simplify `try_format_argument()`, make `manual_indexing_id()` a variable 95e1aa2d add support for manual indexing and named fields, add tests 7e72673d Improve width estimation (#2033) 13b117b5 Improve code point computation ee0fed63 Fix handling of the + flag with locales (#2133) c5979d56 Fix fmt::localtime formatting not working in wide-char string contexts e6ef927e fmt::ptr: Support function pointers (#2131) 58aa0457 Fix ordering of install commands for CMake (#2122) 1980ca8c fix #2118: FMT_COMPILE did not work with tm formatter (#2119) 2a25e2bf Make ranges-test available with C++11 (#2114) b0b56b43 fix #2116 (FMT_COMPILE requires exceptions enabled) (#2117) 373262f9 Update docs ce519e93 Fix exception propagation from iterators (#2097) acef0bb5 use gcc-10.2 instead of gcc-10.1 on CI, also fix one problem (#2110) 8bf28e6b Add support for s format specifier to bool (#2094) (#2109) 9c418bc4 Update README.rst 456efa46 add missing detail namespace (#2107) 80dc7cce Fixed format.h(1465): warning C4702: unreachable code (#2106) 7fd535c6 Cleanup 'L' handling b4b8917c Update docs e4f2cf45 Make 'L' a modifier 6972b5f3 Add build variable: FMT_MASTER_PROJECT (#2100) ac352081 Install fmt/args.h (#2096) 532e846b Fix width computation in float formatter f8c2f848 Fix handling of width when formatting int as char 0fe0b15e Fix handling of # in width computation 061e364b Document output_file 018688da Correct a typo on syntax.rst (documentation) (#2081) 9ec5592b Fix writing to stdout when redirected to NUL on Windows (#2080) cdc5ef67 Remove fallback to inline specifier from FMT_CONSTEXPR(20) macro (#2075) c9dd1eb9 Don't change charset d09b5c14 Fix std::byte formatting with compile-time API (#2072) bbd6ed5b Add support of most format_specs for formatting at compile-time (#2056) a750bf3a Update api.rst 1256541d Fix formatting 4fa4c924 Add tests for FMT_ENFORCE_COMPILE_STRING, fix several errors (#2038) aa89e380 add cwchar to format.h for std::fputws (#2073) 5a37e182 Disable warning about format string (#2067) fa43fd14 Forward arguments to work with views (#2068) 3551f5d1 Workaround a gcc 10 -Warray-bounds bug (#2065) e7376726 Remove an old mingw workaround (#2059) 25a41b80 Fix a link to Android.mk (#2057) 9293f707 Suppress gcc warning on privates-only class (#2053) c20874c2 Reenable support for fallback formatter in join (#2040) (#2050) 5de0bc1d Add UDL as replacement for FMT_COMPILE (#2043) a6fafe2f docs: use https for some links (#2051) 33f9a6d3 Fix handling of enums in to_string (#2036) aabe0a84 simplify tests by reordering arguments of `EXPECT_EQ` (#2044) 1f4a76d2 Add a missing include (#2047) 4a6eadbd Make std::byte formattabe (#1981) 683a7450 fix formatting with empty compiled format string (#2042) f43416e1 Add a link to contents from index 5a493560 Move some code from core.h to format.h where it is used 9ed0a981 Fix docs build dac753b8 Basics of formatting at compile-time based on compile-time API (#2019) 119f7dc3 Truncate file by default 22a68d16 Don't emit trailing zeros by default d0110b7e Update README.rst 3f4839ce Merge branch 'release' of github.com:fmtlib/fmt 7bdf0628 Update version fc135511 Update changelog 926233bd Fix test 0683fa7d Bump version 6ce207b9 Fix formatting 07b1c1a1 Update changelog 58992761 Reintroduce ostream support to range formatters (#2014) b8957f50 Fix an overflow in format_to_n (#2029) df66516e Workaround an issue with mixing std versions in gcc (#2017) a57baa69 Fix more linkage errors (#2011) 85534a13 Fix linkage errors when linking with a shared library (#2011) a2fa5d62 Update changelog cd300368 Fix more linkage errors (#2011) d1ef29d6 Fix initialization of iterator_buffer (#1996) 5f41bb0f clang-format a58a6b27 Add a newline a036cc97 Reintroduce ostream support to range formatters (#2014) 38c7def4 Update clang version to 3.4 since there are ICEs on earlier ones 55336413 🆕 [CI] Test with C++14 in Windows 2019 (#2020) 55dfdd92 Update README.rst 2c734c9b Fix an overflow in format_to_n (#2029) 6cdd1be9 Update build.gradle for latest AGP (#2026) bcc20b29 Implement compile-time checks by default befd7d4a Always use FMT_STRING internally where possible [Issue #2002] (#2006) f8640d40 Add more standards f81c14aa Workaround an issue with mixing std versions in gcc (#2017) 5555651c Fix more linkage errors (#2011) b268f881 detail::write in one more place relevant to printf with long argument… (#2016) aa9b09a9 🐛 Cannot call non-constexpr function in constexpr context (#2010) 986fa004 Printf get container (#1982) 7abc3c01 Suppress a useless warning (#2004) 6d14f781 Fix linkage errors when linking with a shared library (#2011) 9534b9fe Refactor warning suppression 60dc2735 Simplify on_text b5dac0f0 Reduce <algorithm> usage (#1998) a07627b1 🐛 Implicit sign conversion warning in clang in c++17 and 20 modes (#2009) 1b8f499e 🔧 Silence useless cast warnings (#2008) f428d286 Update README.rst beb248b6 Optimize handling of large format strings 1936dddc fix gcc warning of missing override (#2001) 14f6bd0f Move one more headers to args.h e01d26e1 Optimize includes e528d919 Merge branch 'master' of github.com:fmtlib/fmt 48816772 Update signatures 3302fd10 use memchr for searching for '%' in printf format string (#1984) 4c2d6372 Update signatures beaff396 Update signatures ffa0a083 Use newer versions of Sphinx and Breathe 038057eb Document contexts 5bedcb66 Fix initialization of iterator_buffer (#1996) 2435ea41 Workaround MSVC mess 8c6215f5 Fix fmt/color.h 10ebe6cb Document color 1ac50fcb Suppress more bogus warnings e098be8e Fix warning filtering 8cf0afaf Improve docs e29f93e8 Suppress more bogus warnings 4e8d000f Suppress more bogus warnings 7787792e Fix re usage 6ee5e507 Fix imports 06ee32d1 Filter useless doxygen warnings 86bb7fe6 Add a missing import 959a9f5c Merge branch 'master' of github.com:fmtlib/fmt 4f7df299 Improve docs b3ab0bc7 🎨 [CI] Specify the exact version of clang to use (#1991) 701ed6c8 Install deps in github actions instead of script 8f2131cf Document chrono 32c4af8f Document chrono 295a60ec Document chrono a4fae96c Document chrono 263bb0e6 Document chrono 0506b328 Document chrono 4e426c19 Document chrono 9795d873 Update docs 2eb0be0b Remove debug code and fix bot contact cd955798 Move less installation to actions 98639d0f Debug doc build ab5e0632 Debug doc build b123129f Dump the content of html dir 81d2b986 Print less command 7a0b1d57 Add key 9f0617cb Fix branch ref 75b07598 Chrono docs dfbb6975 Remove travis config 5b3052f9 Switch doc build to github actions 506ff320 Fix build failure when not using fcntl with -Werror (#1990) a30b279b Apply clang-format and tweak comments 6a2495c8 -Wattributes visibility warning with some GCC versions (#1975) cba5970c Remove migrated build configs 689081d8 Merge branch 'release' of github.com:fmtlib/fmt cc09f1a6 Update version e4eb242c Update changelog and bump version ce98e0c6 Fix fallback float formatter at assymetric bounds (#1976) 49544ea9 Fuzz fallback formatter 6b7bfed4 Fix fallback float formatter at assymetric bounds (#1976) bcab36da Update CI config 1689e73e Move PR template 0103408a Update CI config 38a16ecb Move build config to github actions 205eb3a8 Update CI config fe61b8c6 Update CI config 867b15d7 Update CI config 98cb9f99 Update CI config 95077d60 Update CI config bc49f094 Update CI config cef6dfb4 Update CI config c8703ba4 Update CI config ab4405be Update README.rst 78a55e28 Update CI config d0a2494a Update cmake.yml 89d009ba Update cmake.yml 1f4ff47b Create cmake.yml eb52ac7a 🆕 Enable -Wshadow in pedantic mode e904e891 🎨 🐛 Rename all shadowed types and variables 771292c3 Remove sizeof from unused variable silencer (#1974) 86bf6045 Merge branch 'release' of github.com:fmtlib/fmt 5f7f7b95 Update version 5d3f0741 Update changelog and bump version 563cbb6c Add a macro to workaround clang/gcc ABI incompatibility on ARM 425778aa Fix ABI compatibility (#1961) 69a84198 Remove accidental parenthesis (#1968) 5c045049 Removed [-Wsign-conversion] warning in GCC 556a1cfb Instantiate to_decimal to make gcc lto happy (#1955) 28a8eae8 Cleanup 236fea1f Workaround bugs in gcc 8 e50ced88 Add a macro to workaround clang/gcc ABI incompatibility on ARM 112755cf Remove FMT_SAFEBUFFERS (#1966) 4081b2fe Fix ABI compatibility (#1961) 2d9311e8 Remove accidental parenthesis (#1968) b3a4f28a Fix implicit signedness conversion warning (#1963) 97c88732 Allocator::max_size support in basic_memory_buffer (#1960) bb68f608 Removed [-Wsign-conversion] warning in GCC f4ca065c Range support cb224eca Instantiate to_decimal to make gcc lto happy (#1955) 7977c2b4 Cleanup e54eb676 Workaround bugs in gcc 8 4fe0b111 Update version df4bd60f Bump version 764fb35e Always install the required version of breathe e1bdc0ec Use the correct version of sphinx 39bde329 Tweak markup 204d299a Tweak markup e0995b1c Update readme 4af178bd Remove outdated build config aa41dc02 Remove unused script 6a77ea3c Tweak markup 62c72059 Update changelog c10e3f7f Update changelog e542e695 Update changelog 530cf316 Point to the release, not dev documentation 740385d6 Update changelog cd465111 Update changelog 46291be3 Update changelog 90071c1d Update ChangeLog.rst 25293d7a Update ChangeLog.rst 5024742f Update ChangeLog.rst 0452a4e7 Update changelog 8de96817 Woraround bugs in gcc 8 47e16767 Simplify arg formatter f0a42346 Move parsing optimization one level up 86287b8d Optimize common case in parse_format_specs 8924211f Update README.rst 525e7649 Update CONTRIBUTING.md 0ecb3d18 Optimize alignment parsing 97553078 Optimize format_uint 7446818f Simplify vformat_to 280b5612 Add option to force usage of inline namespaces e57ec7d5 Merge vformat_to overloads 2a3f4de3 Remove iterator_category 27fdb4ea Unshadow floaty 297e0bad Apply clang-format e3b4c22e Simplify is_output_iterator da8278e1 Update changelog and bump version 17fba753 added position independent documentation (#1939) 71e705a2 Update README.rst 74654c8c Fix compilation for systems without fcntl.h (#1942) f468b203 Avoid conversion from long long to size_t (#1935) 20d4f2e8 Fix handling of weird character types when parsing sign (#1932) 08370c39 Update README.rst bd3c7925 Fix float fuzzer 8d3fd86d Merge branch 'master' of github.com:fmtlib/fmt 40347157 Update README.rst 37d738fa Update README.rst 271eff14 Make classes derived from buffer<T> final to silence the virtual destructor warning. (#1937) 010efc31 Add float fuzzer and cleanup 811c8b58 Add float fuzzer and cleanup 82c4e223 Cleanup fuzzing 63e40c96 Fix naming of fuzzers 2f448ed5 Fix fuzzer timeouts af283059 Cleanup 48ea8193 Explain why assert-test is a separate test 1d112bdd Remove old test 5eb292a6 Update README.rst 7e56b6b6 Fix coding style and remove duplicate fuzzer 41d97e1e Fix a UB on ridiculously large precision 01c37e0a Added check for `-mbig-obj` and ref qualifier check (#1929) a5e7e7db Fix handling of thousand separator (#1927) bf19051a Optimize floating point formatting 3c13a88b Optimize floating point formatting f6d75c53 Refactor write_float e9c0b2d6 Merge write_float overloads 7eddbfed Cleanup exponent handling in write_float b347b302 Update dynamic_formatter comment (#1923) 3541880e Fix integer overflow when using max int precision 7b50dc0b Don't exclude all detail symbols from docs 28052431 Fix the doc config 34f22e88 Cleanup CMake config a18b3fbb Fix fixed precision handling when rounding (#1917) 72770357 Fix long lines in usage.md 7612c1ea Add reference to lhelper package manager in usage b91d39f2 Get rid of float_writer b4b64b9c Refactor float formatting 712abe40 Workaround a bug in gcc 7.5 (#1912) af8a180a Make GetCachedPower test more precise a581e9e5 Fix warning C4018: '<=': signed/unsigned mismatch (#1908) 05a28312 Update docs 4d0aa4d8 Update link 575f4018 Simplify FP formatting and follow coding conventions 6f3536f9 Move zero-check to an earlier branch (#1906) 90ef46df Fix dragonbox integration 3ae88147 Fix declaration 64179525 Improve dragonbox integration 79694d42 Fix WriteConsole signature 51f2e2ca Move nan test to where it belongs 68555fdb Make format-test not depend on color.h 63e0c354 Make dragonbox::to_decimal available in format.h 2213a711 Update README.rst 79ba37f3 Update README.rst a905d8f7 Merge grisu-test into format-test 762c33a9 Simplify windows handling (#1903) 253d6315 Remove dependency on windows.h (#1900) c156093f Fix carry in fallback_format 34179b33 Update format.h (#1898) 0651e459 Minor tweaks to get_cached_power 6c025520 Test that max_k is correctly defined 51f8d0cc Reuse log10_2_significand constant 1305cbeb Fix MSVC2019 error C2049 when compiling with /clr (#1897) 2d4fde3a Don't emit trailing zero for consistency with std::format 5fd89d50 Minor simplifications 605ce5e4 Simplify divisible_by_power_of_2 085171e7 Remove grisu_count_digits aa729bf2 Remove dead code aa2ddf9b Simplify Dragonbox integration c1654ce4 Simplify uint32_or_64_or_128_t definition 33712dc0 Combine pragmas e5942ac9 Tweak comments aae7a133 Remove unused pragmas 6bcde9aa https://github.com/fmtlib/fmt/pull/1882#issuecomment-696823912 (#1894) bb0db5e5 clang-format 16410056 Optimize copy_str for counting_iterator 2591ab91 MSVC optimizations for count_digits. (#1890) d5b8002d Update README.rst 821471e1 qkw: generalizing aliasing | using fmt library and it's features (#1888) 2e620ddb Small improvements that should have zero to negligible impact on the runtime (#1887) 2f7e0885 Disable range formatter if value type is not formattable (#1885) c46a8de4 Simplify test 2696dc92 add forgotten template argument to make_format_args which made some u… (#1877) 0016da7a Don't generate zeros and fix UB on huge precision ce3f7699 Merge intrinsic blocks 3b6248f6 Change formatting 2d9b1dd0 Fix sign mismatch 1f0600a2 Fix bug regarding FMT_SAFEBUFFERS 2ecdbb98 Fix a bug in ctzll 6f81ea15 Fix typo (and thus bug) 0c8ffe9b Implement Dragonbox (first version) 42699bf4 Fix msvc version of clz & clzll (#1880) bc51a8df Disable fallthrough attributes for the Intel compilers on Linux and MacOS (#1879) 45da432d fix compiler warnings in public header files d55e61f1 Improve FMT_ALWAYS_INLINE (#1878) 7e682752 Remove trailing zeros when using fallback formatter (#1873) 1d696dc2 Handle exotic character types in compilation f674434a Add format_to_n overload that accepts FMT_COMPILE (from #1767) (#1869) 5b5a5971 Fix handling of wide alignment f80ed64d Update README.rst 38139664 Simplify fallback format dce8e49b Handle float in fallback formatter 78b59443 Spelling f233b56c Don't generate insignificant digits 595902f8 Update test 4f2ee892 Use built-in FP formatter for any precision 58a044be Use built-in FP formatter for any precision efe3694f Macro tweak and clang-format 9f312fe8 Implement fallback FP formatting with given precision (#1526) fb289cf5 Fix coding conventions 86f0a704 Fix formatting bff4d18e Add color format_to overloads * Fix variable size basic_memory_buffer colorization * Fix an unused arguments warning on GCC that blocks the CI otherwise * Ref #1842 * Ref #1593 f19b8885 Fixed a warning in mingw32/mingw64 (#1860) f8e00a08 NOMINMAX not handled properly (#1855) 6cccdc24 Fix move constructor (#1844) 69902c17 Allow use of <fcntl.h> in Linux when __has_include is not available (#1848) 1edd38b9 Add append mode. (#1847) e66ba169 Added build2 usage instructions. (#1838) f39e6fb6 Add formatters for chrono::time_point<system_clock> (#1837) 77b627be Fix bogus MSVC warnings (#1825) 5dff01d3 Add complex tests d16d585e Update signatures c7e6d8af Fix usage of override (#1836) 92bff2fe Revert "Add missing includes" a0dcfbc5 Add ptr to docs 1651b2d4 Fix detail::write with fallback formatter (#1829) 06895a76 Add missing includes 92a448a0 Apply clang-format 6be65446 Fixing buffer_appender's ++ slicing (#1822) 951e0d23 CMakeLists.txt: Added Wundef warning to clang and gcc. (#1823) f9f02df7 CMakeLists.txt: Clang-warnings: removed -Wno-sign-conversion (#1817) 76e97dc4 Eliminate shadowed variable warnings on intel (#1816) e204df0e nvcc compiler should be EDG-based, but fails test (#1818) 1c8bb547 small changes to reduce clang-9 warnings (#1808) 4b69c787 fix: warning C4100: unreferenced formal parameter (#1814) fb0aeb82 fix: disabled UDL templates for PGI (#1811) (#1812) 54daa086 Add dynamic width support to FMT_COMPILE (#1809) 6fb7c6fb Workaround a bug in gcc10 (#1810) 16985fda Update README.rst 1378ddae Update README.rst 4fd95e4b Don't remove trailing zeros with # e06ae322 Avoid warnings on functions with external linkage that don't have declarations 7fc3d1f5 Add override to grow 065889a5 Use correct capacity in iterator_buffer (#1807) d0dd6786 Adding convenience append(range) 0e7cef06 Merge commit 'c13f79e0' e2c8c455 Update README.rst e4c954ff Update README.rst c13f79e0 Merge release branch d7921d64 Update README.rst 4a4fc225 Update changelog 61602a75 Remove -Wno-shadow 2f8fc29e Update README.rst 717b226b include/fmt/format.h: explicit cast to std::size_t for parameter to buffer.resize() in order to get rid of warning 'implicit conversion changes signedness:' in clang-8 (#1802) 2a69f567 Tweak buffer size ea769338 Simplify ostream 5413713c Remove unused function 57f46242 Increase the default buffer size 0b6e7cc6 Update README.rst e587adb4 Simplify count_digits 279d698e Fix handling of default alignmment with locale (#1801) 76cfb50b Test complex formatter 20829120 Optimize count_digits 8d9ab967 Cut a few cycles from count_digits 73434493 Simplify ostream_params 2a47a1e4 Update README.rst 7c4c5c79 Make buffer size configurable f0b84da5 Don't use 128 bit integers with clang-cl (#1800) a3dfd6f9 Workaround a bug in msvc 51d05521 Workaround broken numeric_limites, part 2 (#1787) 21c8b5c1 Report error on missing named argument (#1796) d82fdcc9 Fix handling of iterators in locale-specific formatting (#1782) 633213d9 Merge release branch e8f2580a Bump version 6cefe55a Update changelog 64e2da15 Update README.rst 1c8c810f Update README.rst c2399ccf Update README.rst a7c5db06 Update README.rst a4c22acd Update README.rst 0c1f4b5a Update README.rst 63b422ee Update README.rst 26e81a67 Update README.rst de5fc6af Update README.rst 9c2edfd1 Partially revert 638db5 because it breaks the doc build 810357c0 Document color 0a7032a4 Update README.rst 95d3abf9 Make format_to_n part of the core API 98626093 Correct the locale format specifier in api.rst (#1792) 47f8d7a3 Make formatted_size part of the core API 46a63b70 Update docs 430f393d Disabled __attribute__((deprecated)) usage for LCC (#1790) febffa4e Make join() handle non-const-only begin/end ranges (#1786) d69e2da2 Fix apidoc ce73ea37 Reorder functions d39d661b Workaround broken numeric_limits (#1725) c228bfe8 Improve docs 38ce19f7 Update README.rst d11849bc Add FMT_REDUCE_INT_INSTANTIATIONS flag (#1781) c08518a2 Move make_args_checked to the public API e2837084 Add a color section 9f0c0033 Simplify format string checks d615137c Improve handling of buffer iterator 26b47b6f Bump tested CMake version to 3.18 7a01c9c5 Update README.rst b17d5c4f Fix a regression in handling digit separators (#1782) eb90da2e Type erase output iterators 9d3cd0af Type erase output iterators 18024853 Fix compatibility with CMake 3.4 (#1779) f5d4215b Trying to clear ambiguous compile time claims (#1775) c26349f4 Improve error reporting f4b11ef6 Add a short anchor 0097cf11 Report unformattable type name more prominently 8fa20b47 dev -> latest a03bd3dd Autodetect MSVC static runtime (#1770) c108ee1d Clarify a comment a8074a86 Update README.rst 5f629548 Update README.rst bd903f96 Clarify precedence 16cac46a Improve handling of streamable and convertible to bool types (#1766) 415cd519 direct_buffered_file -> ostream e1bfb596 Fix handling of code units in compile ba8d98cb Cleanup direct_buffered_file 04a1f6e9 Improve handling of single code units in compile e4f57bfd Add an overload of write for buffer_appender d8704681 Make append work with fixed-size buffer e8ec09ae Cleanup core-test a2c4fed9 Double buffering no more 36406509 Add a fixed buffer 60c43e87 Apply clang-format b998e0f3 Reduce symbol sizes and simplify iterator use c5adfc51 Update README.rst c4ad94ce Update README.rst c1429651 Fix image source link 638db5ca Use Cmake to find Python and Sphinx-doc. c0905697 Update readme 1efdb2dd Simplify readme dc69afad Cleanup example 445f5d39 Break long lines 23063c34 Update readme f57b6257 Move PR template to the top level git-subtree-dir: externals/fmt git-subtree-split: 9e8b86fd2d9806672cc73133d21780dd182bfd24
This commit is contained in:
parent
d4c6fa3122
commit
3e6176d6f6
111 changed files with 44477 additions and 47148 deletions
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
|
@ -1,6 +1,6 @@
|
||||||
<!-- Please read the contribution guidelines before submitting a pull request. -->
|
<!--
|
||||||
<!-- By submitting this pull request, you agree that your contributions are licensed under the {fmt} license,
|
Please read the contribution guidelines before submitting a pull request:
|
||||||
and agree to future changes to the licensing. -->
|
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
|
||||||
<!-- If you're a first-time contributor, please acknowledge it by leaving the statement below. -->
|
By submitting this pull request, you agree that your contributions are licensed
|
||||||
|
under the {fmt} license, and agree to future changes to the licensing.
|
||||||
I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing.
|
-->
|
||||||
|
|
23
.github/workflows/doc.yml
vendored
Normal file
23
.github/workflows/doc.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
name: doc
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: |
|
||||||
|
sudo apt install doxygen python3-virtualenv
|
||||||
|
sudo npm install -g less clean-css
|
||||||
|
cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
env:
|
||||||
|
KEY: ${{secrets.KEY}}
|
||||||
|
run: $GITHUB_WORKSPACE/support/build-docs.py
|
78
.github/workflows/linux.yml
vendored
Normal file
78
.github/workflows/linux.yml
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
name: linux
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
cxx: [g++-4.8, g++-10, clang++-9]
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
std: [11]
|
||||||
|
os: [ubuntu-18.04]
|
||||||
|
include:
|
||||||
|
- cxx: g++-4.8
|
||||||
|
install: sudo apt install g++-4.8
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-8
|
||||||
|
build_type: Debug
|
||||||
|
std: 14
|
||||||
|
install: sudo apt install g++-8
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-10
|
||||||
|
build_type: Debug
|
||||||
|
std: 17
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-10
|
||||||
|
build_type: Debug
|
||||||
|
std: 20
|
||||||
|
os: ubuntu-20.04
|
||||||
|
- cxx: clang++-9
|
||||||
|
build_type: Debug
|
||||||
|
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||||
|
std: 17
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: clang++-11
|
||||||
|
build_type: Debug
|
||||||
|
std: 20
|
||||||
|
os: ubuntu-20.04
|
||||||
|
- cxx: clang++-11
|
||||||
|
build_type: Debug
|
||||||
|
std: 20
|
||||||
|
cxxflags: -stdlib=libc++
|
||||||
|
os: ubuntu-20.04
|
||||||
|
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
||||||
|
- shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: |
|
||||||
|
${{matrix.install}}
|
||||||
|
sudo apt install locales-all
|
||||||
|
cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
env:
|
||||||
|
CXX: ${{matrix.cxx}}
|
||||||
|
CXXFLAGS: ${{matrix.cxxflags}}
|
||||||
|
run: |
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
|
||||||
|
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
|
||||||
|
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||||
|
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
threads=`nproc`
|
||||||
|
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: ctest -C ${{matrix.build_type}}
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
37
.github/workflows/macos.yml
vendored
Normal file
37
.github/workflows/macos.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
name: macos
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: macos-10.15
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
include:
|
||||||
|
- shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||||
|
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||||
|
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
threads=`sysctl -n hw.logicalcpu`
|
||||||
|
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: ctest -C ${{matrix.build_type}}
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
60
.github/workflows/windows.yml
vendored
Normal file
60
.github/workflows/windows.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
name: windows
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
|
||||||
|
# respectively: https://github.com/actions/virtual-environments.
|
||||||
|
os: [windows-2016, windows-2019]
|
||||||
|
platform: [Win32, x64]
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
standard: [11, 17, 20]
|
||||||
|
include:
|
||||||
|
- os: windows-2016
|
||||||
|
platform: Win32
|
||||||
|
build_type: Debug
|
||||||
|
shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
exclude:
|
||||||
|
- os: windows-2016
|
||||||
|
platform: Win32
|
||||||
|
- os: windows-2016
|
||||||
|
standard: 17
|
||||||
|
- os: windows-2016
|
||||||
|
standard: 20
|
||||||
|
- os: windows-2019
|
||||||
|
standard: 11
|
||||||
|
- os: windows-2019
|
||||||
|
standard: 20
|
||||||
|
platform: Win32
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
# Use a bash shell for $GITHUB_WORKSPACE.
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||||
|
-A ${{matrix.platform}} \
|
||||||
|
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
|
||||||
|
$GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||||
|
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: ctest -C ${{matrix.build_type}} -V
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@ gradle/
|
||||||
gradlew*
|
gradlew*
|
||||||
local.properties
|
local.properties
|
||||||
build/
|
build/
|
||||||
|
support/.cxx
|
||||||
|
|
||||||
bin/
|
bin/
|
||||||
/_CPack_Packages
|
/_CPack_Packages
|
||||||
|
|
101
.travis.yml
101
.travis.yml
|
@ -1,101 +0,0 @@
|
||||||
language: cpp
|
|
||||||
dist: trusty
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
os: linux
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- secure: |-
|
|
||||||
a1eovNn4uol9won7ghr67eD3/59oeESN+G9bWE+ecI1V6yRseG9whniGhIpC/YfMW/Qz5I
|
|
||||||
5sxSmFjaw9bxCISNwUIrL1O5x2AmRYTnFcXk4dFsUvlZg+WeF/aKyBYCNRM8C2ndbBmtAO
|
|
||||||
o1F2EwFbiso0EmtzhAPs19ujiVxkLn4=
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# Documentation
|
|
||||||
- env: BUILD=Doc
|
|
||||||
sudo: required
|
|
||||||
# g++ 6 on Linux with C++14
|
|
||||||
- env: COMPILER=g++-6 BUILD=Debug STANDARD=14
|
|
||||||
compiler: gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-6
|
|
||||||
- env: COMPILER=g++-6 BUILD=Release STANDARD=14
|
|
||||||
compiler: gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-6
|
|
||||||
# g++ 8 on Linux with C++17
|
|
||||||
- env: COMPILER=g++-8 BUILD=Debug STANDARD=17
|
|
||||||
compiler: gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-8
|
|
||||||
- env: COMPILER=g++-8 BUILD=Release STANDARD=17
|
|
||||||
compiler: gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-8
|
|
||||||
|
|
||||||
# Apple clang on OS X with C++14
|
|
||||||
- env: BUILD=Debug STANDARD=14
|
|
||||||
compiler: clang
|
|
||||||
os: osx
|
|
||||||
- env: BUILD=Release STANDARD=14
|
|
||||||
compiler: clang
|
|
||||||
os: osx
|
|
||||||
# clang 6.0 on Linux with C++14 (builds the fuzzers as well)
|
|
||||||
- env: COMPILER=clang++-6.0 BUILD=Debug STANDARD=14 ENABLE_FUZZING=1
|
|
||||||
compiler: clang
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
packages:
|
|
||||||
- clang-6.0
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
- llvm-toolchain-trusty
|
|
||||||
- llvm-toolchain-trusty-6.0
|
|
||||||
# clang 4.0 on Linux with C++14
|
|
||||||
- env: COMPILER=clang++-4.0 BUILD=Debug STANDARD=11
|
|
||||||
compiler: clang
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
packages:
|
|
||||||
- clang-4.0
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
- llvm-toolchain-trusty
|
|
||||||
- llvm-toolchain-trusty-4.0
|
|
||||||
# g++ 4.8 on Linux with C++11
|
|
||||||
- env: COMPILER=g++-4.8 BUILD=Debug STANDARD=11
|
|
||||||
compiler: gcc
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then export CXX=${COMPILER}; fi
|
|
||||||
- if [[ "${BUILD}" != "Doc" ]]; then ${CXX} --version; fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- support/travis-build.py
|
|
141
CMakeLists.txt
141
CMakeLists.txt
|
@ -1,18 +1,18 @@
|
||||||
cmake_minimum_required(VERSION 3.1.0)
|
cmake_minimum_required(VERSION 3.1...3.18)
|
||||||
|
|
||||||
# Use newer policies if available, up to most recent tested version of CMake.
|
# Fallback for using newer policies on CMake <3.12.
|
||||||
if(${CMAKE_VERSION} VERSION_LESS 3.11)
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||||
else()
|
|
||||||
cmake_policy(VERSION 3.11)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Determine if fmt is built as a subproject (using add_subdirectory)
|
# Determine if fmt is built as a subproject (using add_subdirectory)
|
||||||
# or if it is the master project.
|
# or if it is the master project.
|
||||||
set(MASTER_PROJECT OFF)
|
if (NOT DEFINED FMT_MASTER_PROJECT)
|
||||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
set(FMT_MASTER_PROJECT OFF)
|
||||||
set(MASTER_PROJECT ON)
|
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
set(FMT_MASTER_PROJECT ON)
|
||||||
|
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||||
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Joins arguments and places the results in ${result_var}.
|
# Joins arguments and places the results in ${result_var}.
|
||||||
|
@ -24,6 +24,17 @@ function(join result_var)
|
||||||
set(${result_var} "${result}" PARENT_SCOPE)
|
set(${result_var} "${result}" PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
function(enable_module target)
|
||||||
|
if (MSVC)
|
||||||
|
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||||
|
target_compile_options(${target}
|
||||||
|
PRIVATE /interface /ifcOutput ${BMI}
|
||||||
|
INTERFACE /reference fmt=${BMI})
|
||||||
|
endif ()
|
||||||
|
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||||
|
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
include(CMakeParseArguments)
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
# Sets a cache variable with a docstring joined from multiple arguments:
|
# Sets a cache variable with a docstring joined from multiple arguments:
|
||||||
|
@ -46,7 +57,7 @@ endfunction()
|
||||||
# Set the default CMAKE_BUILD_TYPE to Release.
|
# Set the default CMAKE_BUILD_TYPE to Release.
|
||||||
# This should be done before the project command since the latter can set
|
# This should be done before the project command since the latter can set
|
||||||
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
||||||
if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||||
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
|
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
|
||||||
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||||
|
@ -55,20 +66,36 @@ endif ()
|
||||||
project(FMT CXX)
|
project(FMT CXX)
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
|
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
|
||||||
"Installation directory for include files, a relative path "
|
"Installation directory for include files, a relative path that "
|
||||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||||
|
|
||||||
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||||
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||||
OFF)
|
OFF)
|
||||||
|
|
||||||
# Options that control generation of various targets.
|
# Options that control generation of various targets.
|
||||||
option(FMT_DOC "Generate the doc target." ${MASTER_PROJECT})
|
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
|
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||||
option(FMT_TEST "Generate the test target." ${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)
|
||||||
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||||
|
option(FMT_MODULE "Build a module instead of a traditional library." 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 ON)
|
||||||
|
endif ()
|
||||||
|
if (NOT FMT_CAN_MODULE)
|
||||||
|
set(FMT_MODULE OFF)
|
||||||
|
message(STATUS "Module support is disabled.")
|
||||||
|
endif ()
|
||||||
|
if (FMT_TEST AND FMT_MODULE)
|
||||||
|
# The tests require {fmt} to be compiled as traditional library
|
||||||
|
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||||
|
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)
|
||||||
|
@ -104,24 +131,36 @@ if (${index} GREATER -1)
|
||||||
endif ()
|
endif ()
|
||||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||||
|
|
||||||
|
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||||
|
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||||
|
"Preset for the export of private symbols")
|
||||||
|
set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS
|
||||||
|
hidden default)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN)
|
||||||
|
set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL
|
||||||
|
"Whether to add a compile flag to hide symbols of inline functions")
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
||||||
-Wold-style-cast -Wundef
|
-Wold-style-cast -Wundef
|
||||||
-Wredundant-decls -Wwrite-strings -Wpointer-arith
|
-Wredundant-decls -Wwrite-strings -Wpointer-arith
|
||||||
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
|
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
|
||||||
-Wcast-align -Wnon-virtual-dtor
|
-Wcast-align
|
||||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||||
-Winvalid-pch -Woverloaded-virtual
|
-Winvalid-pch -Woverloaded-virtual
|
||||||
-Wconversion -Wswitch-enum
|
-Wconversion -Wswitch-enum -Wundef
|
||||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow)
|
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
|
||||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||||
-Wno-dangling-else -Wno-unused-local-typedefs)
|
-Wno-dangling-else -Wno-unused-local-typedefs)
|
||||||
endif ()
|
endif ()
|
||||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
||||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
|
||||||
-Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
|
-Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
|
||||||
-Wvector-operation-performance -Wsized-deallocation)
|
-Wvector-operation-performance -Wsized-deallocation -Wshadow)
|
||||||
endif ()
|
endif ()
|
||||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||||
|
@ -131,8 +170,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion
|
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
|
||||||
-Wno-sign-conversion -Wdeprecated -Wweak-vtables)
|
-Wdeprecated -Wweak-vtables -Wshadow
|
||||||
|
-Wno-gnu-zero-variadic-macro-arguments)
|
||||||
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||||
if (HAS_NULLPTR_WARNING)
|
if (HAS_NULLPTR_WARNING)
|
||||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||||
|
@ -146,7 +186,7 @@ if (MSVC)
|
||||||
set(WERROR_FLAG /WX)
|
set(WERROR_FLAG /WX)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||||
# If Microsoft SDK is installed create script run-msbuild.bat that
|
# If Microsoft SDK is installed create script run-msbuild.bat that
|
||||||
# calls SetEnv.cmd to set up build environment and runs msbuild.
|
# calls SetEnv.cmd to set up build environment and runs msbuild.
|
||||||
# It is useful when building Visual Studio projects with the SDK
|
# It is useful when building Visual Studio projects with the SDK
|
||||||
|
@ -185,9 +225,12 @@ function(add_headers VAR)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# Define the fmt library, its includes and the needed defines.
|
# Define the fmt library, its includes and the needed defines.
|
||||||
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
|
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||||
locale.h os.h ostream.h posix.h printf.h ranges.h)
|
format-inl.h locale.h os.h ostream.h printf.h ranges.h
|
||||||
if (FMT_OS)
|
xchar.h)
|
||||||
|
if (FMT_MODULE)
|
||||||
|
set(FMT_SOURCES src/fmt.cc)
|
||||||
|
elseif (FMT_OS)
|
||||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||||
else()
|
else()
|
||||||
set(FMT_SOURCES src/format.cc)
|
set(FMT_SOURCES src/format.cc)
|
||||||
|
@ -201,7 +244,10 @@ if (HAVE_STRTOD_L)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
|
||||||
|
if (${FMT_HAS_MBIG_OBJ})
|
||||||
|
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||||
|
endif()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (FMT_WERROR)
|
if (FMT_WERROR)
|
||||||
|
@ -210,6 +256,9 @@ 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})
|
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||||
|
|
||||||
|
@ -231,7 +280,8 @@ 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 NOT EMSCRIPTEN)
|
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
|
||||||
|
NOT EMSCRIPTEN)
|
||||||
# Fix rpmlint warning:
|
# Fix rpmlint warning:
|
||||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||||
target_link_libraries(fmt -Wl,--as-needed)
|
target_link_libraries(fmt -Wl,--as-needed)
|
||||||
|
@ -256,20 +306,22 @@ target_include_directories(fmt-header-only INTERFACE
|
||||||
if (FMT_INSTALL)
|
if (FMT_INSTALL)
|
||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||||
"Installation directory for cmake files, a relative path "
|
"Installation directory for cmake files, a relative path that "
|
||||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
|
||||||
|
"path.")
|
||||||
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||||
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||||
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
||||||
set(targets_export_name fmt-targets)
|
set(targets_export_name fmt-targets)
|
||||||
|
|
||||||
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||||
"Installation directory for libraries, a relative path "
|
"Installation directory for libraries, a relative path that "
|
||||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary 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 PATH
|
||||||
"Installation directory for pkgconfig (.pc) files, a relative path "
|
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||||
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.")
|
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||||
|
"absolute path.")
|
||||||
|
|
||||||
# Generate the version, config and target files into the build directory.
|
# Generate the version, config and target files into the build directory.
|
||||||
write_basic_package_version_file(
|
write_basic_package_version_file(
|
||||||
|
@ -290,6 +342,13 @@ if (FMT_INSTALL)
|
||||||
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||||
|
|
||||||
set(INSTALL_TARGETS fmt fmt-header-only)
|
set(INSTALL_TARGETS fmt fmt-header-only)
|
||||||
|
|
||||||
|
# Install the library and headers.
|
||||||
|
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||||
|
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||||
|
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||||
|
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
|
||||||
# imported targets.
|
# imported targets.
|
||||||
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
||||||
|
@ -302,12 +361,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 the library and headers.
|
|
||||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
|
||||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
|
||||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|
||||||
|
|
||||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||||
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
|
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
|
||||||
|
@ -326,11 +379,17 @@ endif ()
|
||||||
# Control fuzzing independent of the unit tests.
|
# Control fuzzing independent of the unit tests.
|
||||||
if (FMT_FUZZ)
|
if (FMT_FUZZ)
|
||||||
add_subdirectory(test/fuzzing)
|
add_subdirectory(test/fuzzing)
|
||||||
|
|
||||||
|
# The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
|
||||||
|
# mode and make fuzzing practically possible. It is similar to
|
||||||
|
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
|
||||||
|
# avoid interfering with fuzzing of projects that use {fmt}.
|
||||||
|
# See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
|
||||||
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||||
if (MASTER_PROJECT AND EXISTS ${gitignore})
|
if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
|
||||||
# Get the list of ignored files from .gitignore.
|
# Get the list of ignored files from .gitignore.
|
||||||
file (STRINGS ${gitignore} lines)
|
file (STRINGS ${gitignore} lines)
|
||||||
list(REMOVE_ITEM lines /doc/html)
|
list(REMOVE_ITEM lines /doc/html)
|
||||||
|
|
|
@ -14,4 +14,7 @@ exceptions:
|
||||||
* snake_case should be used instead of UpperCamelCase for function and type
|
* snake_case should be used instead of UpperCamelCase for function and type
|
||||||
names
|
names
|
||||||
|
|
||||||
|
All documentation must adhere to the [Google Developer Documentation Style
|
||||||
|
Guide](https://developers.google.com/style).
|
||||||
|
|
||||||
Thanks for contributing!
|
Thanks for contributing!
|
||||||
|
|
1090
ChangeLog.rst
1090
ChangeLog.rst
File diff suppressed because it is too large
Load diff
298
README.rst
298
README.rst
|
@ -1,54 +1,69 @@
|
||||||
{fmt}
|
{fmt}
|
||||||
=====
|
=====
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master
|
.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
|
||||||
:target: https://travis-ci.org/fmtlib/fmt
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
|
||||||
|
|
||||||
|
.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
|
||||||
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
|
||||||
|
|
||||||
|
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
||||||
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
||||||
|
|
||||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||||
|
|
||||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
|
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
||||||
:alt: fmt is continuously fuzzed att 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?\
|
||||||
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
|
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
|
||||||
Summary&q=proj%3Dlibfmt&can=1
|
Summary&q=proj%3Dfmt&can=1
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
||||||
: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
|
||||||
|
|
||||||
**{fmt}** is an open-source formatting library for C++.
|
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||||
It can be used as a safe and fast alternative to (s)printf and iostreams.
|
alternative to C stdio and C++ iostreams.
|
||||||
|
|
||||||
`Documentation <https://fmt.dev/latest/>`__
|
If you like this project, please consider donating to the BYSOL
|
||||||
|
Foundation that helps victims of political repressions in Belarus:
|
||||||
|
https://bysol.org/en/bs/general/.
|
||||||
|
|
||||||
|
`Documentation <https://fmt.dev>`__
|
||||||
|
|
||||||
Q&A: ask questions on `StackOverflow with the tag fmt
|
Q&A: ask questions on `StackOverflow with the tag fmt
|
||||||
<https://stackoverflow.com/questions/tagged/fmt>`_.
|
<https://stackoverflow.com/questions/tagged/fmt>`_.
|
||||||
|
|
||||||
|
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* Simple `format API <https://fmt.dev/dev/api.html>`_ with positional arguments
|
* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
|
||||||
for localization
|
for localization
|
||||||
* Implementation of `C++20 std::format
|
* Implementation of `C++20 std::format
|
||||||
<https://en.cppreference.com/w/cpp/utility/format>`__
|
<https://en.cppreference.com/w/cpp/utility/format>`__
|
||||||
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
|
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
||||||
of 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
|
||||||
|
round-trip guarantees
|
||||||
* Safe `printf implementation
|
* Safe `printf implementation
|
||||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
|
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
||||||
the POSIX extension for positional arguments
|
extension for positional arguments
|
||||||
* Extensibility: support for user-defined types
|
* Extensibility: `support for user-defined types
|
||||||
|
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
|
||||||
* High performance: faster than common standard library implementations of
|
* High performance: faster than common standard library implementations of
|
||||||
`printf <https://en.cppreference.com/w/cpp/io/c/fprintf>`_,
|
``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
|
||||||
iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ and
|
and `Converting a hundred million integers to strings per second
|
||||||
`Converting a hundred million integers to strings per second
|
|
||||||
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
||||||
* Small code size both in terms of source code (the minimum configuration
|
* Small code size both in terms of source code with the minimum configuration
|
||||||
consists of just three header files, ``core.h``, ``format.h`` and
|
consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
|
||||||
``format-inl.h``) and compiled code. See `Compile time and code bloat`_
|
and compiled code; see `Compile time and code bloat`_
|
||||||
* Reliability: the library has an extensive set of `unit tests
|
* Reliability: the library has an extensive set of `tests
|
||||||
<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
|
||||||
|
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
|
||||||
|
@ -57,18 +72,17 @@ Features
|
||||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||||
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||||
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
|
* 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
|
||||||
* Support for wide strings
|
|
||||||
* 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/latest/>`_ for more details.
|
See the `documentation <https://fmt.dev>`_ for more details.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Print ``Hello, world!`` to ``stdout``:
|
**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
|
@ -78,100 +92,95 @@ Print ``Hello, world!`` to ``stdout``:
|
||||||
fmt::print("Hello, world!\n");
|
fmt::print("Hello, world!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
Format a string:
|
**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
std::string s = fmt::format("The answer is {}.", 42);
|
std::string s = fmt::format("The answer is {}.", 42);
|
||||||
// s == "The answer is 42."
|
// s == "The answer is 42."
|
||||||
|
|
||||||
Format a string using positional arguments:
|
**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||||
// s == "I'd rather be happy than right."
|
// s == "I'd rather be happy than right."
|
||||||
|
|
||||||
Print a chrono duration:
|
**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
using namespace std::chrono_literals;
|
using namespace std::literals::chrono_literals;
|
||||||
fmt::print("Elapsed time: {}", 42ms);
|
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||||
|
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||||
}
|
}
|
||||||
|
|
||||||
prints "Elapsed time: 42ms".
|
Output::
|
||||||
|
|
||||||
Check a format string at compile time:
|
Default format: 42s 100ms
|
||||||
|
strftime-like format: 03:15:30
|
||||||
|
|
||||||
|
**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
// test.cc
|
#include <vector>
|
||||||
#include <fmt/format.h>
|
#include <fmt/ranges.h>
|
||||||
std::string s = format(FMT_STRING("{:d}"), "hello");
|
|
||||||
|
|
||||||
gives a compile-time error because ``d`` is an invalid format specifier for a
|
int main() {
|
||||||
string.
|
std::vector<int> v = {1, 2, 3};
|
||||||
|
fmt::print("{}\n", v);
|
||||||
Use {fmt} as a safe portable replacement for ``itoa``
|
|
||||||
(`godbolt <https://godbolt.org/g/NXmpU4>`_):
|
|
||||||
|
|
||||||
.. code:: c++
|
|
||||||
|
|
||||||
fmt::memory_buffer buf;
|
|
||||||
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
|
|
||||||
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
|
|
||||||
// access the string with to_string(buf) or buf.data()
|
|
||||||
|
|
||||||
Format objects of user-defined types via a simple `extension API
|
|
||||||
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_:
|
|
||||||
|
|
||||||
.. code:: c++
|
|
||||||
|
|
||||||
#include <fmt/format.h>
|
|
||||||
|
|
||||||
struct date {
|
|
||||||
int year, month, day;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct fmt::formatter<date> {
|
|
||||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const date& d, FormatContext& ctx) {
|
|
||||||
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
|
|
||||||
// s == "The date is 2012-12-9"
|
|
||||||
|
|
||||||
Create your own functions similar to `format
|
|
||||||
<https://fmt.dev/latest/api.html#format>`_ and
|
|
||||||
`print <https://fmt.dev/latest/api.html#print>`_
|
|
||||||
which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
|
|
||||||
|
|
||||||
.. code:: c++
|
|
||||||
|
|
||||||
// Prints formatted error message.
|
|
||||||
void vreport_error(const char* format, fmt::format_args args) {
|
|
||||||
fmt::print("Error: ");
|
|
||||||
fmt::vprint(format, args);
|
|
||||||
}
|
|
||||||
template <typename... Args>
|
|
||||||
void report_error(const char* format, const Args & ... args) {
|
|
||||||
vreport_error(format, fmt::make_format_args(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
report_error("file not found: {}", path);
|
Output::
|
||||||
|
|
||||||
Note that ``vreport_error`` is not parameterized on argument types which can
|
[1, 2, 3]
|
||||||
improve compile times and reduce code size compared to a fully parameterized
|
|
||||||
version.
|
**Check a format string at compile time**
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number");
|
||||||
|
|
||||||
|
This gives a compile-time error because ``d`` is an invalid format specifier for
|
||||||
|
a string.
|
||||||
|
|
||||||
|
**Write a file from a single thread**
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/os.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto out = fmt::output_file("guide.txt");
|
||||||
|
out.print("Don't {}", "Panic");
|
||||||
|
}
|
||||||
|
|
||||||
|
This can be `5 to 9 times faster than fprintf
|
||||||
|
<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
|
||||||
|
|
||||||
|
**Print with colors and text styles**
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/color.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
|
||||||
|
"Hello, {}!\n", "world");
|
||||||
|
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
|
||||||
|
fmt::emphasis::underline, "Hello, {}!\n", "мир");
|
||||||
|
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
|
||||||
|
"Hello, {}!\n", "世界");
|
||||||
|
}
|
||||||
|
|
||||||
|
Output on a modern terminal:
|
||||||
|
|
||||||
|
.. image:: https://user-images.githubusercontent.com/
|
||||||
|
576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
|
||||||
|
|
||||||
Benchmarks
|
Benchmarks
|
||||||
----------
|
----------
|
||||||
|
@ -198,12 +207,14 @@ 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/tinyformat_test.cpp>`_.
|
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
|
||||||
|
|
||||||
{fmt} is up to 10x 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>`_)
|
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||||
and faster than `double-conversion <https://github.com/google/double-conversion>`_:
|
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
||||||
|
`ryu <https://github.com/ulfjack/ryu>`_:
|
||||||
|
|
||||||
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
|
.. image:: https://user-images.githubusercontent.com/576385/
|
||||||
:target: https://fmt.dev/unknown_mac64_clang10.0.html
|
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
|
||||||
|
:target: https://fmt.dev/unknown_mac64_clang12.0.html
|
||||||
|
|
||||||
Compile time and code bloat
|
Compile time and code bloat
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -278,34 +289,46 @@ Then you can run the speed test::
|
||||||
or the bloat test::
|
or the bloat test::
|
||||||
|
|
||||||
$ make bloat-test
|
$ make bloat-test
|
||||||
|
|
||||||
|
Migrating code
|
||||||
|
--------------
|
||||||
|
|
||||||
|
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
|
||||||
|
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
|
||||||
|
``fmt::print``.
|
||||||
|
|
||||||
Projects using this library
|
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
|
||||||
|
|
||||||
* `Aseprite <https://github.com/aseprite/aseprite>`_:
|
* `Aseprite <https://github.com/aseprite/aseprite>`_:
|
||||||
Animated sprite editor & pixel art tool
|
animated sprite editor & pixel art tool
|
||||||
|
|
||||||
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft
|
* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
|
||||||
operations suite
|
operations suite
|
||||||
|
|
||||||
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space
|
* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
|
||||||
|
|
||||||
|
* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
|
||||||
|
|
||||||
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
|
* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
|
||||||
|
|
||||||
* `ccache <https://ccache.dev/>`_: A compiler cache
|
* `ccache <https://ccache.dev/>`_: a compiler cache
|
||||||
|
|
||||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database management system
|
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
|
||||||
|
management system
|
||||||
|
|
||||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||||
vehicle
|
vehicle
|
||||||
|
|
||||||
* `Drake <https://drake.mit.edu/>`_: A planning, control, and analysis toolbox
|
* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
|
||||||
for nonlinear dynamical systems (MIT)
|
for nonlinear dynamical systems (MIT)
|
||||||
|
|
||||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||||
|
@ -313,71 +336,82 @@ Projects using this library
|
||||||
|
|
||||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||||
|
|
||||||
|
* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
|
||||||
|
logging library with latency in nanoseconds
|
||||||
|
|
||||||
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
||||||
|
|
||||||
|
* `Grand Mountain Adventure
|
||||||
|
<https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/>`_:
|
||||||
|
A beautiful open-world ski & snowboarding game
|
||||||
|
|
||||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||||
Player vs Player Gaming Network with tweaks
|
Player vs Player Gaming Network with tweaks
|
||||||
|
|
||||||
* `KBEngine <https://kbengine.org/>`_: An open-source MMOG server engine
|
* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
|
||||||
|
engine
|
||||||
|
|
||||||
* `Keypirinha <https://keypirinha.com/>`_: A semantic launcher for Windows
|
* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
|
||||||
|
|
||||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
|
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
|
||||||
|
|
||||||
* `Knuth <https://kth.cash/>`_: High-performance Bitcoin full-node
|
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
||||||
|
|
||||||
* `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
|
||||||
|
|
||||||
* `MongoDB <https://mongodb.com/>`_: Distributed document database
|
* `MongoDB <https://mongodb.com/>`_: distributed document database
|
||||||
|
|
||||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
|
||||||
generate randomized datasets
|
generate randomized datasets
|
||||||
|
|
||||||
* `OpenSpace <https://openspaceproject.com/>`_: An open-source
|
* `OpenSpace <https://openspaceproject.com/>`_: an open-source
|
||||||
astrovisualization framework
|
astrovisualization framework
|
||||||
|
|
||||||
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
||||||
An MMO server, compatible with most Ultima Online clients
|
an MMO server, compatible with most Ultima Online clients
|
||||||
|
|
||||||
* `PyTorch <https://github.com/pytorch/pytorch>`_: An open-source machine
|
* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
|
||||||
learning library
|
learning library
|
||||||
|
|
||||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
|
* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
|
||||||
associative database
|
associative database
|
||||||
|
|
||||||
|
* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
|
||||||
|
|
||||||
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
|
* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
|
||||||
|
navigation, and executing complex multi-line terminal command sequences
|
||||||
|
|
||||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
|
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
|
||||||
proxy
|
proxy
|
||||||
|
|
||||||
* `redpanda <https://vectorized.io/redpanda>`_: A 10x faster Kafka® replacement
|
* `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
|
||||||
|
|
||||||
* `Salesforce Analytics Cloud
|
* `Salesforce Analytics Cloud
|
||||||
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||||
Business intelligence software
|
business intelligence software
|
||||||
|
|
||||||
* `Scylla <https://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store
|
* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
|
||||||
that can handle 1 million transactions per second on a single server
|
that can handle 1 million transactions per second on a single server
|
||||||
|
|
||||||
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++
|
* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
|
||||||
framework for high-performance server applications on modern hardware
|
framework for high-performance server applications on modern hardware
|
||||||
|
|
||||||
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
|
* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
|
||||||
|
|
||||||
* `Stellar <https://www.stellar.org/>`_: Financial platform
|
* `Stellar <https://www.stellar.org/>`_: financial platform
|
||||||
|
|
||||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
|
* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
|
||||||
|
|
||||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
||||||
MMORPG framework
|
MMORPG framework
|
||||||
|
|
||||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: The new Windows
|
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
||||||
Terminal
|
terminal
|
||||||
|
|
||||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||||
|
|
||||||
|
@ -435,7 +469,7 @@ Boost Format
|
||||||
|
|
||||||
This is a very powerful library which supports both ``printf``-like format
|
This is a very powerful library which 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
|
||||||
`Benchmarks`_).
|
`Benchmarks`_).
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,14 @@ if (NOT DOXYGEN)
|
||||||
return ()
|
return ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
find_package(PythonInterp QUIET REQUIRED)
|
||||||
|
|
||||||
add_custom_target(doc
|
add_custom_target(doc
|
||||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION}
|
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
|
||||||
|
${FMT_VERSION}
|
||||||
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
|
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
||||||
DESTINATION share/doc/fmt OPTIONAL
|
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL
|
||||||
PATTERN ".doctrees" EXCLUDE)
|
PATTERN ".doctrees" EXCLUDE)
|
||||||
|
|
289
doc/api.rst
289
doc/api.rst
|
@ -6,17 +6,18 @@ 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 argument handling
|
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
|
||||||
facilities and a lightweight subset of formatting functions
|
for ``char``/UTF-8 with compile-time checks and minimal dependencies
|
||||||
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
|
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
|
||||||
format string checks, wide string, output iterator and user-defined type
|
formatting functions and locale support
|
||||||
support
|
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
||||||
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
|
|
||||||
and tuples
|
|
||||||
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
||||||
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
||||||
|
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
||||||
|
* :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/printf.h <printf-api>`: ``printf`` formatting
|
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||||
|
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
|
||||||
|
|
||||||
All functions and types provided by the library reside in namespace ``fmt`` and
|
All functions and types provided by the library reside in namespace ``fmt`` and
|
||||||
macros have prefix ``FMT_``.
|
macros have prefix ``FMT_``.
|
||||||
|
@ -26,34 +27,56 @@ macros have prefix ``FMT_``.
|
||||||
Core API
|
Core API
|
||||||
========
|
========
|
||||||
|
|
||||||
``fmt/core.h`` defines the core API which provides argument handling facilities
|
``fmt/core.h`` defines the core API which provides main formatting functions for
|
||||||
and a lightweight subset of formatting functions. In the header-only mode
|
``char``/UTF-8 with compile-time checks. It has minimal include dependencies for
|
||||||
include ``fmt/format.h`` instead of ``fmt/core.h``.
|
better compile times. This header is only beneficial when using {fmt} as a
|
||||||
|
library and not in the header-only mode.
|
||||||
|
|
||||||
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
|
||||||
<http://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
||||||
They take *format_str* and *args* as arguments.
|
They take *fmt* and *args* as arguments.
|
||||||
|
|
||||||
*format_str* is a format string that contains literal text and replacement
|
*fmt* is a format string that contains literal text and replacement
|
||||||
fields surrounded by braces ``{}``. The fields are replaced with formatted
|
fields surrounded by braces ``{}``. The fields are replaced with formatted
|
||||||
arguments in the resulting string. A function taking *format_str* doesn't
|
arguments in the resulting string. A function taking *fmt* doesn't
|
||||||
participate in an overload resolution if the latter is not a string.
|
participate in an overload resolution if the latter is not a string.
|
||||||
|
|
||||||
*args* is an argument list representing objects to be formatted.
|
*args* is an argument list representing objects to be formatted.
|
||||||
|
|
||||||
.. _format:
|
.. _format:
|
||||||
|
|
||||||
.. doxygenfunction:: format(const S&, Args&&...)
|
.. doxygenfunction:: format(format_string<T...> fmt, T&&... args) -> std::string
|
||||||
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<type_identity_t<Char>>>)
|
.. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string
|
||||||
|
|
||||||
|
.. doxygenfunction:: format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt
|
||||||
|
.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string<T...> fmt, const T&... args) -> format_to_n_result<OutputIt>
|
||||||
|
.. doxygenfunction:: formatted_size(format_string<T...> fmt, T&&... args) -> size_t
|
||||||
|
|
||||||
|
.. doxygenstruct:: fmt::format_to_n_result
|
||||||
|
:members:
|
||||||
|
|
||||||
.. _print:
|
.. _print:
|
||||||
|
|
||||||
.. doxygenfunction:: print(const S&, Args&&...)
|
.. doxygenfunction:: fmt::print(format_string<T...> fmt, T&&... args)
|
||||||
.. doxygenfunction:: vprint(string_view, format_args)
|
.. doxygenfunction:: vprint(string_view fmt, format_args args)
|
||||||
|
|
||||||
.. doxygenfunction:: print(std::FILE *, const S&, Args&&...)
|
.. doxygenfunction:: print(std::FILE *f, format_string<T...> fmt, T&&... args)
|
||||||
.. doxygenfunction:: vprint(std::FILE *, string_view, format_args)
|
.. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args)
|
||||||
|
|
||||||
|
Compile-time Format String Checks
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in
|
||||||
|
and string types as well as user-defined types with ``constexpr`` ``parse``
|
||||||
|
functions in their ``formatter`` specializations.
|
||||||
|
|
||||||
|
.. doxygendefine:: FMT_STRING
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
Named Arguments
|
Named Arguments
|
||||||
---------------
|
---------------
|
||||||
|
@ -65,6 +88,35 @@ Named arguments are not supported in compile-time checks at the moment.
|
||||||
Argument Lists
|
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_args_checked<Args...>(format, 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_args_checked(const S&, const remove_reference_t<Args>&...)
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||||
|
|
||||||
.. doxygenclass:: fmt::format_arg_store
|
.. doxygenclass:: fmt::format_arg_store
|
||||||
|
@ -76,11 +128,16 @@ Argument Lists
|
||||||
.. doxygenclass:: fmt::basic_format_args
|
.. doxygenclass:: fmt::basic_format_args
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. doxygenstruct:: fmt::format_args
|
.. doxygentypedef:: fmt::format_args
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_arg
|
.. doxygenclass:: fmt::basic_format_arg
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_context
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::format_context
|
||||||
|
|
||||||
Compatibility
|
Compatibility
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -88,12 +145,11 @@ Compatibility
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. doxygentypedef:: fmt::string_view
|
.. doxygentypedef:: fmt::string_view
|
||||||
.. doxygentypedef:: fmt::wstring_view
|
|
||||||
|
|
||||||
Locale
|
Locale
|
||||||
------
|
------
|
||||||
|
|
||||||
All formatting is locale-independent by default. Use the ``'n'`` format
|
All formatting is locale-independent by default. Use the ``'L'`` format
|
||||||
specifier to insert the appropriate number separator characters from the
|
specifier to insert the appropriate number separator characters from the
|
||||||
locale::
|
locale::
|
||||||
|
|
||||||
|
@ -108,17 +164,10 @@ locale::
|
||||||
Format API
|
Format API
|
||||||
==========
|
==========
|
||||||
|
|
||||||
``fmt/format.h`` defines the full format API providing compile-time format
|
``fmt/format.h`` defines the full format API providing additional formatting
|
||||||
string checks, wide string, output iterator and user-defined type support.
|
functions and locale support.
|
||||||
|
|
||||||
Compile-time Format String Checks
|
.. _udt:
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
Compile-time checks are supported for built-in and string types as well as
|
|
||||||
user-defined types with ``constexpr`` ``parse`` functions in their ``formatter``
|
|
||||||
specializations.
|
|
||||||
|
|
||||||
.. doxygendefine:: FMT_STRING
|
|
||||||
|
|
||||||
Formatting User-defined Types
|
Formatting User-defined Types
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
@ -130,14 +179,12 @@ template and implement ``parse`` and ``format`` methods::
|
||||||
|
|
||||||
struct point { double x, y; };
|
struct point { double x, y; };
|
||||||
|
|
||||||
template <>
|
template <> struct fmt::formatter<point> {
|
||||||
struct fmt::formatter<point> {
|
|
||||||
// Presentation format: 'f' - fixed, 'e' - exponential.
|
// Presentation format: 'f' - fixed, 'e' - exponential.
|
||||||
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) {
|
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
// auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11
|
|
||||||
// [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
|
||||||
|
@ -164,8 +211,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>
|
template <typename FormatContext>
|
||||||
auto format(const point& p, FormatContext& ctx) {
|
auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
// auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11
|
|
||||||
// ctx.out() is an output iterator to write to.
|
// ctx.out() is an output iterator to write to.
|
||||||
return format_to(
|
return format_to(
|
||||||
ctx.out(),
|
ctx.out(),
|
||||||
|
@ -237,44 +283,36 @@ You can also write a formatter for a hierarchy of classes::
|
||||||
fmt::print("{}", a); // prints "B"
|
fmt::print("{}", a); // prints "B"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
If a type provides both a ``formatter`` specialization and an implicit
|
||||||
|
conversion to a formattable type, the specialization takes precedence over the
|
||||||
|
conversion.
|
||||||
|
|
||||||
.. doxygenclass:: fmt::basic_format_parse_context
|
.. doxygenclass:: fmt::basic_format_parse_context
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Output Iterator Support
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...)
|
|
||||||
.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, const S&, const Args&...)
|
|
||||||
.. doxygenstruct:: fmt::format_to_n_result
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Literal-based API
|
Literal-based API
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
The following user-defined literals are defined in ``fmt/format.h``.
|
The following user-defined literals are defined in ``fmt/format.h``.
|
||||||
|
|
||||||
.. doxygenfunction:: operator""_format(const char *, size_t)
|
.. doxygenfunction:: operator""_format(const char *s, size_t n) -> detail::udl_formatter<char>
|
||||||
|
|
||||||
.. doxygenfunction:: operator""_a(const char *, size_t)
|
.. doxygenfunction:: operator""_a(const char *s, size_t) -> detail::udl_arg<char>
|
||||||
|
|
||||||
Utilities
|
Utilities
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. doxygenstruct:: fmt::is_char
|
.. doxygenfunction:: fmt::ptr(T p) -> const void*
|
||||||
|
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
|
||||||
|
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
|
||||||
|
|
||||||
.. doxygentypedef:: fmt::char_t
|
.. doxygenfunction:: fmt::to_string(const T &value) -> std::string
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...)
|
.. doxygenfunction:: fmt::to_string_view(const Char *s) -> basic_string_view<Char>
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::to_string(const T&)
|
.. doxygenfunction:: fmt::join(Range &&range, string_view sep) -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>>
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::to_wstring(const T&)
|
.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::to_string_view(const Char *)
|
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::join(const Range&, string_view)
|
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::join(It, Sentinel, string_view)
|
|
||||||
|
|
||||||
.. doxygenclass:: fmt::detail::buffer
|
.. doxygenclass:: fmt::detail::buffer
|
||||||
:members:
|
:members:
|
||||||
|
@ -286,20 +324,14 @@ Utilities
|
||||||
System Errors
|
System Errors
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
fmt does not use ``errno`` to communicate errors to the user, but it may call
|
{fmt} does not use ``errno`` to communicate errors to the user, but it may call
|
||||||
system functions which set ``errno``. Users should not make any assumptions about
|
system functions which set ``errno``. Users should not make any assumptions
|
||||||
the value of ``errno`` being preserved by library functions.
|
about the value of ``errno`` being preserved by library functions.
|
||||||
|
|
||||||
.. doxygenclass:: fmt::system_error
|
.. doxygenfunction:: fmt::system_error
|
||||||
:members:
|
|
||||||
|
|
||||||
.. doxygenfunction:: fmt::format_system_error
|
.. doxygenfunction:: fmt::format_system_error
|
||||||
|
|
||||||
.. doxygenclass:: fmt::windows_error
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. _formatstrings:
|
|
||||||
|
|
||||||
Custom Allocators
|
Custom Allocators
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -330,10 +362,10 @@ allocator::
|
||||||
return vformat(alloc, format_str, fmt::make_format_args(args...));
|
return vformat(alloc, format_str, fmt::make_format_args(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
The allocator will be used for the output container only. If you are using named
|
The allocator will be used for the output container only. Formatting functions
|
||||||
arguments, the container that stores pointers to them will be allocated using
|
normally don't do any allocations for built-in and string types except for
|
||||||
the default allocator. Also floating-point formatting falls back on ``sprintf``
|
non-default floating-point formatting that occasionally falls back on
|
||||||
which may do allocations.
|
``sprintf``.
|
||||||
|
|
||||||
.. _ranges-api:
|
.. _ranges-api:
|
||||||
|
|
||||||
|
@ -365,41 +397,84 @@ Using ``fmt::join``, you can separate tuple elements with a custom separator::
|
||||||
Date and Time Formatting
|
Date and Time Formatting
|
||||||
========================
|
========================
|
||||||
|
|
||||||
The library supports `strftime
|
``fmt/chrono.h`` provides formatters for
|
||||||
<http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like date and time
|
|
||||||
formatting::
|
* `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
|
||||||
|
* `std::chrono::time_point
|
||||||
|
<https://en.cppreference.com/w/cpp/chrono/time_point>`_
|
||||||
|
* `std::tm <https://en.cppreference.com/w/cpp/chrono/c/tm>`_
|
||||||
|
|
||||||
|
The format syntax is described in :ref:`chrono-specs`.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
std::time_t t = std::time(nullptr);
|
int main() {
|
||||||
// Prints "The date is 2016-04-29." (with the current date)
|
std::time_t t = std::time(nullptr);
|
||||||
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
|
||||||
|
|
||||||
The format string syntax is described in the documentation of
|
// Prints "The date is 2020-11-07." (with the current date):
|
||||||
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
|
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
|
// Prints "Default format: 42s 100ms":
|
||||||
|
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||||
|
|
||||||
|
// Prints "strftime-like format: 03:15:30":
|
||||||
|
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.. doxygenfunction:: localtime(std::time_t time)
|
||||||
|
|
||||||
|
.. doxygenfunction:: gmtime(std::time_t time)
|
||||||
|
|
||||||
.. _compile-api:
|
.. _compile-api:
|
||||||
|
|
||||||
Format string compilation
|
Format string compilation
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
``fmt/compile.h`` provides format string compilation support. Format strings
|
``fmt/compile.h`` provides format string compilation support when using
|
||||||
are parsed at compile time and converted into efficient formatting code. This
|
``FMT_COMPILE``. Format strings are parsed, checked and converted into efficient
|
||||||
supports arguments of built-in and string types as well as user-defined types
|
formatting code at compile-time. This supports arguments of built-in and string
|
||||||
with ``constexpr`` ``parse`` functions in their ``formatter`` specializations.
|
types as well as user-defined types with ``constexpr`` ``parse`` functions in
|
||||||
Format string compilation can generate more binary code compared to the default
|
their ``formatter`` specializations. Format string compilation can generate more
|
||||||
API and is only recommended in places where formatting is a performance
|
binary code compared to the default API and is only recommended in places where
|
||||||
bottleneck.
|
formatting is a performance bottleneck.
|
||||||
|
|
||||||
.. doxygendefine:: FMT_COMPILE
|
.. doxygendefine:: FMT_COMPILE
|
||||||
|
|
||||||
|
.. _color-api:
|
||||||
|
|
||||||
|
Terminal color and text style
|
||||||
|
=============================
|
||||||
|
|
||||||
|
``fmt/color.h`` provides support for terminal color and text style output.
|
||||||
|
|
||||||
|
.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args)
|
||||||
|
|
||||||
|
.. doxygenfunction:: fg(detail::color_type)
|
||||||
|
|
||||||
|
.. doxygenfunction:: bg(detail::color_type)
|
||||||
|
|
||||||
|
.. _os-api:
|
||||||
|
|
||||||
|
System APIs
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::ostream
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::windows_error
|
||||||
|
:members:
|
||||||
|
|
||||||
.. _ostream-api:
|
.. _ostream-api:
|
||||||
|
|
||||||
``std::ostream`` Support
|
``std::ostream`` Support
|
||||||
========================
|
========================
|
||||||
|
|
||||||
``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
|
``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
|
||||||
user-defined types that have overloaded ``operator<<``::
|
user-defined types that have an overloaded insertion operator (``operator<<``)::
|
||||||
|
|
||||||
#include <fmt/ostream.h>
|
#include <fmt/ostream.h>
|
||||||
|
|
||||||
|
@ -416,7 +491,10 @@ user-defined types that have overloaded ``operator<<``::
|
||||||
std::string s = fmt::format("The date is {}", date(2012, 12, 9));
|
std::string s = fmt::format("The date is {}", date(2012, 12, 9));
|
||||||
// s == "The date is 2012-12-9"
|
// s == "The date is 2012-12-9"
|
||||||
|
|
||||||
.. doxygenfunction:: print(std::basic_ostream<Char>&, const S&, Args&&...)
|
{fmt} only supports insertion operators that are defined in the same namespaces
|
||||||
|
as the types they format and can be found with the argument-dependent lookup.
|
||||||
|
|
||||||
|
.. doxygenfunction:: print(std::basic_ostream<Char> &os, const S &format_str, Args&&... args)
|
||||||
|
|
||||||
.. _printf-api:
|
.. _printf-api:
|
||||||
|
|
||||||
|
@ -425,18 +503,32 @@ user-defined types that have overloaded ``operator<<``::
|
||||||
|
|
||||||
The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
|
The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
|
||||||
The following functions use `printf format string syntax
|
The following functions use `printf format string syntax
|
||||||
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
<https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||||
the POSIX extension for positional arguments. Unlike their standard
|
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&, const Args&...)
|
.. doxygenfunction:: printf(const S &format_str, const T&... args)
|
||||||
|
|
||||||
.. doxygenfunction:: fprintf(std::FILE *, const S&, const Args&...)
|
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
|
||||||
|
|
||||||
.. doxygenfunction:: fprintf(std::basic_ostream<Char>&, const S&, const Args&...)
|
.. doxygenfunction:: sprintf(const S&, const T&...)
|
||||||
|
|
||||||
.. doxygenfunction:: sprintf(const S&, const Args&...)
|
.. _xchar-api:
|
||||||
|
|
||||||
|
``wchar_t`` Support
|
||||||
|
===================
|
||||||
|
|
||||||
|
The optional header ``fmt/wchar_t.h`` provides support for ``wchar_t`` and
|
||||||
|
exotic character types.
|
||||||
|
|
||||||
|
.. doxygenstruct:: fmt::is_char
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::wstring_view
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::wformat_context
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::to_wstring(const T &value)
|
||||||
|
|
||||||
Compatibility with C++20 ``std::format``
|
Compatibility with C++20 ``std::format``
|
||||||
========================================
|
========================================
|
||||||
|
@ -447,9 +539,6 @@ differences:
|
||||||
|
|
||||||
* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
|
* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
|
||||||
collisions with standard library implementations.
|
collisions with standard library implementations.
|
||||||
* The ``'L'`` format specifier cannot be combined with presentation specifiers
|
|
||||||
yet.
|
|
||||||
* Width calculation doesn't use grapheme clusterization. The latter has been
|
* Width calculation doesn't use grapheme clusterization. The latter has been
|
||||||
implemented in a separate branch but hasn't been integrated yet.
|
implemented in a separate branch but hasn't been integrated yet.
|
||||||
* Chrono formatting doesn't support C++20 date types since they are not provided
|
* Most C++20 chrono types are not supported yet.
|
||||||
by standard library implementations.
|
|
||||||
|
|
113
doc/build.py
113
doc/build.py
|
@ -1,63 +1,33 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
# Build the documentation.
|
# Build the documentation.
|
||||||
|
|
||||||
from __future__ import print_function
|
import errno, os, re, sys
|
||||||
import errno, os, shutil, sys, tempfile
|
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
|
||||||
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3']
|
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']
|
||||||
|
|
||||||
def pip_install(package, commit=None, **kwargs):
|
class Pip:
|
||||||
"Install package using pip."
|
def __init__(self, venv_dir):
|
||||||
min_version = kwargs.get('min_version')
|
self.path = os.path.join(venv_dir, 'bin', 'pip')
|
||||||
if min_version:
|
|
||||||
from pkg_resources import get_distribution, DistributionNotFound
|
|
||||||
try:
|
|
||||||
installed_version = get_distribution(os.path.basename(package)).version
|
|
||||||
if LooseVersion(installed_version) >= min_version:
|
|
||||||
print('{} {} already installed'.format(package, min_version))
|
|
||||||
return
|
|
||||||
except DistributionNotFound:
|
|
||||||
pass
|
|
||||||
if commit:
|
|
||||||
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
|
|
||||||
print('Installing {0}'.format(package))
|
|
||||||
check_call(['pip', 'install', package])
|
|
||||||
|
|
||||||
def create_build_env(dirname='virtualenv'):
|
def install(self, package, commit=None):
|
||||||
|
"Install package using pip."
|
||||||
|
if commit:
|
||||||
|
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
|
||||||
|
print('Installing {0}'.format(package))
|
||||||
|
check_call([self.path, 'install', package])
|
||||||
|
|
||||||
|
def create_build_env(venv_dir='virtualenv'):
|
||||||
# Create virtualenv.
|
# Create virtualenv.
|
||||||
if not os.path.exists(dirname):
|
if not os.path.exists(venv_dir):
|
||||||
check_call(['virtualenv', dirname])
|
check_call(['python3', '-m', 'venv', venv_dir])
|
||||||
import sysconfig
|
# Install Sphinx and Breathe. Require the exact version of Sphinx which is
|
||||||
scripts_dir = os.path.basename(sysconfig.get_path('scripts'))
|
# compatible with Breathe.
|
||||||
activate_this_file = os.path.join(dirname, scripts_dir, 'activate_this.py')
|
pip = Pip(venv_dir)
|
||||||
with open(activate_this_file) as f:
|
pip.install('wheel')
|
||||||
exec(f.read(), dict(__file__=activate_this_file))
|
pip.install('six')
|
||||||
# Import get_distribution after activating virtualenv to get info about
|
pip.install('sphinx-doc/sphinx', 'v3.3.0')
|
||||||
# the correct packages.
|
pip.install('michaeljones/breathe', 'v4.16.0')
|
||||||
from pkg_resources import get_distribution, DistributionNotFound
|
|
||||||
# Upgrade pip because installation of sphinx with pip 1.1 available on Travis
|
|
||||||
# is broken (see #207) and it doesn't support the show command.
|
|
||||||
pip_version = get_distribution('pip').version
|
|
||||||
if LooseVersion(pip_version) < LooseVersion('1.5.4'):
|
|
||||||
print("Updating pip")
|
|
||||||
check_call(['pip', 'install', '--upgrade', 'pip'])
|
|
||||||
# Upgrade distribute because installation of sphinx with distribute 0.6.24
|
|
||||||
# available on Travis is broken (see #207).
|
|
||||||
try:
|
|
||||||
distribute_version = get_distribution('distribute').version
|
|
||||||
if LooseVersion(distribute_version) <= LooseVersion('0.6.24'):
|
|
||||||
print("Updating distribute")
|
|
||||||
check_call(['pip', 'install', '--upgrade', 'distribute'])
|
|
||||||
except DistributionNotFound:
|
|
||||||
pass
|
|
||||||
# Install Sphinx and Breathe.
|
|
||||||
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee',
|
|
||||||
min_version='1.4.1.dev20160531')
|
|
||||||
pip_install('michaeljones/breathe',
|
|
||||||
'129222318f7c8f865d2631e7da7b033567e7f56a',
|
|
||||||
min_version='4.2.0')
|
|
||||||
|
|
||||||
def build_docs(version='dev', **kwargs):
|
def build_docs(version='dev', **kwargs):
|
||||||
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
|
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
@ -66,16 +36,17 @@ def build_docs(version='dev', **kwargs):
|
||||||
'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt'))
|
'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt'))
|
||||||
# Build docs.
|
# Build docs.
|
||||||
cmd = ['doxygen', '-']
|
cmd = ['doxygen', '-']
|
||||||
p = Popen(cmd, stdin=PIPE)
|
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
||||||
doxyxml_dir = os.path.join(work_dir, 'doxyxml')
|
doxyxml_dir = os.path.join(work_dir, 'doxyxml')
|
||||||
p.communicate(input=r'''
|
out, _ = p.communicate(input=r'''
|
||||||
PROJECT_NAME = fmt
|
PROJECT_NAME = fmt
|
||||||
GENERATE_LATEX = NO
|
GENERATE_LATEX = NO
|
||||||
GENERATE_MAN = NO
|
GENERATE_MAN = NO
|
||||||
GENERATE_RTF = NO
|
GENERATE_RTF = NO
|
||||||
CASE_SENSE_NAMES = NO
|
CASE_SENSE_NAMES = NO
|
||||||
INPUT = {0}/core.h {0}/compile.h {0}/format.h {0}/os.h \
|
INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
|
||||||
{0}/ostream.h {0}/printf.h {0}/time.h
|
{0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \
|
||||||
|
{0}/xchar.h
|
||||||
QUIET = YES
|
QUIET = YES
|
||||||
JAVADOC_AUTOBRIEF = YES
|
JAVADOC_AUTOBRIEF = YES
|
||||||
AUTOLINK_SUPPORT = NO
|
AUTOLINK_SUPPORT = NO
|
||||||
|
@ -86,6 +57,7 @@ def build_docs(version='dev', **kwargs):
|
||||||
ALIASES += "endrst=\endverbatim"
|
ALIASES += "endrst=\endverbatim"
|
||||||
MACRO_EXPANSION = YES
|
MACRO_EXPANSION = YES
|
||||||
PREDEFINED = _WIN32=1 \
|
PREDEFINED = _WIN32=1 \
|
||||||
|
__linux__=1 \
|
||||||
FMT_USE_VARIADIC_TEMPLATES=1 \
|
FMT_USE_VARIADIC_TEMPLATES=1 \
|
||||||
FMT_USE_RVALUE_REFERENCES=1 \
|
FMT_USE_RVALUE_REFERENCES=1 \
|
||||||
FMT_USE_USER_DEFINED_LITERALS=1 \
|
FMT_USE_USER_DEFINED_LITERALS=1 \
|
||||||
|
@ -94,20 +66,37 @@ def build_docs(version='dev', **kwargs):
|
||||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||||
"FMT_END_NAMESPACE=}}" \
|
"FMT_END_NAMESPACE=}}" \
|
||||||
"FMT_STRING_ALIAS=1" \
|
"FMT_STRING_ALIAS=1" \
|
||||||
"FMT_ENABLE_IF(B)="
|
"FMT_DOC=1"
|
||||||
EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str
|
EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \
|
||||||
|
fmt::basic_format_arg::handle
|
||||||
'''.format(include_dir, doxyxml_dir).encode('UTF-8'))
|
'''.format(include_dir, doxyxml_dir).encode('UTF-8'))
|
||||||
|
out = out.decode('utf-8')
|
||||||
|
internal_symbols = [
|
||||||
|
'fmt::detail::.*',
|
||||||
|
'basic_data<>',
|
||||||
|
'fmt::type_identity',
|
||||||
|
'fmt::dynamic_formatter'
|
||||||
|
]
|
||||||
|
noisy_warnings = [
|
||||||
|
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
|
||||||
|
') is not documented.',
|
||||||
|
'warning: Internal inconsistency: .* does not belong to any container!'
|
||||||
|
]
|
||||||
|
for w in noisy_warnings:
|
||||||
|
out = re.sub('.*' + w + '\n', '', out)
|
||||||
|
print(out)
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise CalledProcessError(p.returncode, cmd)
|
raise CalledProcessError(p.returncode, cmd)
|
||||||
|
|
||||||
html_dir = os.path.join(work_dir, 'html')
|
html_dir = os.path.join(work_dir, 'html')
|
||||||
main_versions = reversed(versions[-3:])
|
main_versions = reversed(versions[-3:])
|
||||||
check_call(['sphinx-build',
|
check_call([os.path.join(work_dir, 'virtualenv', 'bin', 'sphinx-build'),
|
||||||
'-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir),
|
'-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir),
|
||||||
'-Dversion=' + version, '-Drelease=' + version,
|
'-Dversion=' + version, '-Drelease=' + version,
|
||||||
'-Aversion=' + version, '-Aversions=' + ','.join(main_versions),
|
'-Aversion=' + version, '-Aversions=' + ','.join(main_versions),
|
||||||
'-b', 'html', doc_dir, html_dir])
|
'-b', 'html', doc_dir, html_dir])
|
||||||
try:
|
try:
|
||||||
check_call(['lessc', '--clean-css',
|
check_call(['lessc', '--verbose', '--clean-css',
|
||||||
'--include-path=' + os.path.join(doc_dir, 'bootstrap'),
|
'--include-path=' + os.path.join(doc_dir, 'bootstrap'),
|
||||||
os.path.join(doc_dir, 'fmt.less'),
|
os.path.join(doc_dir, 'fmt.less'),
|
||||||
os.path.join(html_dir, '_static', 'fmt.css')])
|
os.path.join(html_dir, '_static', 'fmt.css')])
|
||||||
|
|
|
@ -56,6 +56,11 @@ div.sphinxsidebar {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override center alignment in tables.
|
||||||
|
td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
p.rubric {
|
p.rubric {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,24 +23,26 @@ Format API
|
||||||
|
|
||||||
The format API is similar in spirit to the C ``printf`` family of function but
|
The format API is similar in spirit to the C ``printf`` family of function but
|
||||||
is safer, simpler and several times `faster
|
is safer, simpler and several times `faster
|
||||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
|
<https://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
||||||
than common standard library implementations.
|
than common standard library implementations.
|
||||||
The `format string syntax <syntax.html>`_ is similar to the one used by
|
The `format string syntax <syntax.html>`_ is similar to the one used by
|
||||||
`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_ in
|
`str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ in
|
||||||
Python:
|
Python:
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
fmt::format("The answer is {}.", 42);
|
std::string s = fmt::format("The answer is {}.", 42);
|
||||||
|
|
||||||
The ``fmt::format`` function returns a string "The answer is 42.". You can use
|
The ``fmt::format`` function returns a string "The answer is 42.". You can use
|
||||||
``fmt::memory_buffer`` to avoid constructing ``std::string``:
|
``fmt::memory_buffer`` to avoid constructing ``std::string``:
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
fmt::memory_buffer out;
|
auto out = fmt::memory_buffer();
|
||||||
format_to(out, "For a moment, {} happened.", "nothing");
|
format_to(std::back_inserter(out),
|
||||||
out.data(); // returns a pointer to the formatted data
|
"For a moment, {} happened.", "nothing");
|
||||||
|
auto data = out.data(); // pointer to the formatted data
|
||||||
|
auto size = out.size(); // size of the formatted data
|
||||||
|
|
||||||
The ``fmt::print`` function performs formatting and writes the result to a stream:
|
The ``fmt::print`` function performs formatting and writes the result to a stream:
|
||||||
|
|
||||||
|
@ -48,21 +50,19 @@ The ``fmt::print`` function performs formatting and writes the result to a strea
|
||||||
|
|
||||||
fmt::print(stderr, "System error code = {}\n", errno);
|
fmt::print(stderr, "System error code = {}\n", errno);
|
||||||
|
|
||||||
The file argument can be omitted in which case the function prints to
|
If you omit the file argument the function will print to ``stdout``:
|
||||||
``stdout``:
|
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
fmt::print("Don't {}\n", "panic");
|
fmt::print("Don't {}\n", "panic");
|
||||||
|
|
||||||
The Format API also supports positional arguments useful for localization:
|
The format API also supports positional arguments useful for localization:
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
fmt::print("I'd rather be {1} than {0}.", "right", "happy");
|
fmt::print("I'd rather be {1} than {0}.", "right", "happy");
|
||||||
|
|
||||||
Named arguments can be created with ``fmt::arg``. This makes it easier to track
|
You can pass named arguments with ``fmt::arg``:
|
||||||
what goes where when multiple arguments are being formatted:
|
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
|
@ -91,16 +91,17 @@ time. For example, the code
|
||||||
|
|
||||||
fmt::format("The answer is {:d}", "forty-two");
|
fmt::format("The answer is {:d}", "forty-two");
|
||||||
|
|
||||||
throws a ``format_error`` exception with description "unknown format code 'd' for
|
throws the ``format_error`` exception because the argument ``"forty-two"`` is a
|
||||||
string", because the argument ``"forty-two"`` is a string while the format code
|
string while the format code ``d`` only applies to integers.
|
||||||
``d`` only applies to integers, while
|
|
||||||
|
The code
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
|
||||||
format(FMT_STRING("The answer is {:d}"), "forty-two");
|
format(FMT_STRING("The answer is {:d}"), "forty-two");
|
||||||
|
|
||||||
reports a compile-time error for the same reason on compilers that support
|
reports a compile-time error on compilers that support relaxed ``constexpr``.
|
||||||
relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for details.
|
See `here <api.html#c.fmt>`_ for details.
|
||||||
|
|
||||||
The following code
|
The following code
|
||||||
|
|
||||||
|
@ -109,21 +110,15 @@ The following code
|
||||||
fmt::format("Cyrillic letter {}", L'\x42e');
|
fmt::format("Cyrillic letter {}", L'\x42e');
|
||||||
|
|
||||||
produces a compile-time error because wide character ``L'\x42e'`` cannot be
|
produces a compile-time error because wide character ``L'\x42e'`` cannot be
|
||||||
formatted into a narrow string. You can use a wide format string instead:
|
formatted into a narrow string. For comparison, writing a wide character to
|
||||||
|
``std::ostream`` results in its numeric value being written to the stream
|
||||||
.. code:: c++
|
(i.e. 1070 instead of letter 'ю' which is represented by ``L'\x42e'`` if we
|
||||||
|
use Unicode) which is rarely desirable.
|
||||||
fmt::format(L"Cyrillic letter {}", L'\x42e');
|
|
||||||
|
|
||||||
For comparison, writing a wide character to ``std::ostream`` results in
|
|
||||||
its numeric value being written to the stream (i.e. 1070 instead of letter 'ю'
|
|
||||||
which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is
|
|
||||||
needed.
|
|
||||||
|
|
||||||
Compact Binary Code
|
Compact Binary Code
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The library is designed to produce compact per-call compiled code. For example
|
The library produces compact per-call compiled code. For example
|
||||||
(`godbolt <https://godbolt.org/g/TZU4KF>`_),
|
(`godbolt <https://godbolt.org/g/TZU4KF>`_),
|
||||||
|
|
||||||
.. code:: c++
|
.. code:: c++
|
||||||
|
@ -144,8 +139,8 @@ compiles to just
|
||||||
mov rcx, rsp
|
mov rcx, rsp
|
||||||
mov edi, offset .L.str
|
mov edi, offset .L.str
|
||||||
mov esi, 17
|
mov esi, 17
|
||||||
mov edx, 2
|
mov edx, 1
|
||||||
call fmt::v5::vprint(fmt::v5::basic_string_view<char>, fmt::v5::format_args)
|
call fmt::v7::vprint(fmt::v7::basic_string_view<char>, fmt::v7::format_args)
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
add rsp, 24
|
add rsp, 24
|
||||||
ret
|
ret
|
||||||
|
@ -167,20 +162,19 @@ The library is highly portable and relies only on a small set of C++11 features:
|
||||||
* deleted functions
|
* deleted functions
|
||||||
* alias templates
|
* alias templates
|
||||||
|
|
||||||
These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older
|
These are available in GCC 4.8, Clang 3.4, MSVC 19.0 (2015) and more recent
|
||||||
compilers use {fmt} `version 4.x
|
compiler version. For older compilers use {fmt} `version 4.x
|
||||||
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be
|
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which is maintained and
|
||||||
maintained and only requires C++98.
|
only requires C++98.
|
||||||
|
|
||||||
The output of all formatting functions is consistent across platforms. In
|
The output of all formatting functions is consistent across platforms.
|
||||||
particular, formatting a floating-point infinity always gives ``inf`` while the
|
For example,
|
||||||
output of ``printf`` is platform-dependent. For example,
|
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
fmt::print("{}", std::numeric_limits<double>::infinity());
|
fmt::print("{}", std::numeric_limits<double>::infinity());
|
||||||
|
|
||||||
always prints ``inf``.
|
always prints ``inf`` while the output of ``printf`` is platform-dependent.
|
||||||
|
|
||||||
.. _ease-of-use:
|
.. _ease-of-use:
|
||||||
|
|
||||||
|
@ -192,11 +186,13 @@ just three header files and no external dependencies.
|
||||||
A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows
|
A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows
|
||||||
using the library both in open-source and commercial projects.
|
using the library both in open-source and commercial projects.
|
||||||
|
|
||||||
|
`Learn more... <contents.html>`_
|
||||||
|
|
||||||
.. raw:: html
|
.. raw:: html
|
||||||
|
|
||||||
<a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a>
|
<a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a>
|
||||||
|
|
||||||
<div class="section footer">
|
<div class="section footer">
|
||||||
<iframe src="http://ghbtns.com/github-btn.html?user=fmtlib&repo=fmt&type=watch&count=true"
|
<iframe src="https://ghbtns.com/github-btn.html?user=fmtlib&repo=fmt&type=watch&count=true"
|
||||||
class="github-btn" width="100" height="20"></iframe>
|
class="github-btn" width="100" height="20"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@ literal text, it can be escaped by doubling: ``{{`` and ``}}``.
|
||||||
The grammar for a replacement field is as follows:
|
The grammar for a replacement field is as follows:
|
||||||
|
|
||||||
.. productionlist:: sf
|
.. productionlist:: sf
|
||||||
replacement_field: "{" [`arg_id`] [":" `format_spec`] "}"
|
replacement_field: "{" [`arg_id`] [":" (`format_spec` | `chrono_format_spec`)] "}"
|
||||||
arg_id: `integer` | `identifier`
|
arg_id: `integer` | `identifier`
|
||||||
integer: `digit`+
|
integer: `digit`+
|
||||||
digit: "0"..."9"
|
digit: "0"..."9"
|
||||||
|
@ -27,8 +27,8 @@ The grammar for a replacement field is as follows:
|
||||||
In less formal terms, the replacement field can start with an *arg_id*
|
In less formal terms, the replacement field can start with an *arg_id*
|
||||||
that specifies the argument whose value is to be formatted and inserted into
|
that specifies the argument whose value is to be formatted and inserted into
|
||||||
the output instead of the replacement field.
|
the output instead of the replacement field.
|
||||||
The *arg_id* is optionally followed by a *format_spec*, which is preceded
|
The *arg_id* is optionally followed by a *format_spec*, which is preceded by a
|
||||||
by a colon ``':'``. These specify a non-default format for the replacement value.
|
colon ``':'``. These specify a non-default format for the replacement value.
|
||||||
|
|
||||||
See also the :ref:`formatspec` section.
|
See also the :ref:`formatspec` section.
|
||||||
|
|
||||||
|
@ -75,14 +75,14 @@ although some of the formatting options are only supported by the numeric types.
|
||||||
The general form of a *standard format specifier* is:
|
The general form of a *standard format specifier* is:
|
||||||
|
|
||||||
.. productionlist:: sf
|
.. productionlist:: sf
|
||||||
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`]
|
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`]["L"][`type`]
|
||||||
fill: <a character other than '{' or '}'>
|
fill: <a character other than '{' or '}'>
|
||||||
align: "<" | ">" | "^"
|
align: "<" | ">" | "^"
|
||||||
sign: "+" | "-" | " "
|
sign: "+" | "-" | " "
|
||||||
width: `integer` | "{" [`arg_id`] "}"
|
width: `integer` | "{" [`arg_id`] "}"
|
||||||
precision: `integer` | "{" [`arg_id`] "}"
|
precision: `integer` | "{" [`arg_id`] "}"
|
||||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s"
|
type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" |
|
||||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
: "o" | "p" | "s" | "x" | "X"
|
||||||
|
|
||||||
The *fill* character can be any Unicode code point other than ``'{'`` or
|
The *fill* character can be any Unicode code point other than ``'{'`` or
|
||||||
``'}'``. The presence of a fill character is signaled by the character following
|
``'}'``. The presence of a fill character is signaled by the character following
|
||||||
|
@ -163,6 +163,9 @@ indicates the maximum field size - in other words, how many characters will be
|
||||||
used from the field content. The *precision* is not allowed for integer,
|
used from the field content. The *precision* is not allowed for integer,
|
||||||
character, Boolean, and pointer values.
|
character, Boolean, and pointer values.
|
||||||
|
|
||||||
|
The ``'L'`` option uses the current locale setting to insert the appropriate
|
||||||
|
number separator characters. This option is only valid for numeric types.
|
||||||
|
|
||||||
Finally, the *type* determines how the data should be presented.
|
Finally, the *type* determines how the data should be presented.
|
||||||
|
|
||||||
The available string presentation types are:
|
The available string presentation types are:
|
||||||
|
@ -200,6 +203,8 @@ The available integer presentation types are:
|
||||||
| | ``'#'`` option with this type adds the prefix ``"0B"`` |
|
| | ``'#'`` option with this type adds the prefix ``"0B"`` |
|
||||||
| | to the output value. |
|
| | to the output value. |
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
|
| ``'c'`` | Character format. Outputs the number as a character. |
|
||||||
|
+---------+----------------------------------------------------------+
|
||||||
| ``'d'`` | Decimal integer. Outputs the number in base 10. |
|
| ``'d'`` | Decimal integer. Outputs the number in base 10. |
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
| ``'o'`` | Octal format. Outputs the number in base 8. |
|
| ``'o'`` | Octal format. Outputs the number in base 8. |
|
||||||
|
@ -214,10 +219,6 @@ The available integer presentation types are:
|
||||||
| | ``'#'`` option with this type adds the prefix ``"0X"`` |
|
| | ``'#'`` option with this type adds the prefix ``"0X"`` |
|
||||||
| | to the output value. |
|
| | to the output value. |
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
| ``'L'`` | Locale-specific format. This is the same as ``'d'``, |
|
|
||||||
| | except that it uses the current locale setting to insert |
|
|
||||||
| | the appropriate number separator characters. |
|
|
||||||
+---------+----------------------------------------------------------+
|
|
||||||
| none | The same as ``'d'``. |
|
| none | The same as ``'d'``. |
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
|
|
||||||
|
@ -261,14 +262,8 @@ The available presentation types for floating-point values are:
|
||||||
| | ``'E'`` if the number gets too large. The |
|
| | ``'E'`` if the number gets too large. The |
|
||||||
| | representations of infinity and NaN are uppercased, too. |
|
| | representations of infinity and NaN are uppercased, too. |
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
| ``'L'`` | Locale-specific format. This is the same as ``'g'``, |
|
| none | Similar to ``'g'``, except that the default precision is |
|
||||||
| | except that it uses the current locale setting to insert |
|
| | as high as needed to represent the particular value. |
|
||||||
| | the appropriate number separator characters. |
|
|
||||||
+---------+----------------------------------------------------------+
|
|
||||||
| none | Similar to ``'g'``, except that fixed-point notation, |
|
|
||||||
| | when used, has at least one digit past the decimal |
|
|
||||||
| | point. The default precision is as high as needed to |
|
|
||||||
| | represent the particular value. |
|
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
|
|
||||||
.. ifconfig:: False
|
.. ifconfig:: False
|
||||||
|
@ -303,6 +298,59 @@ The available presentation types for pointers are:
|
||||||
| none | The same as ``'p'``. |
|
| none | The same as ``'p'``. |
|
||||||
+---------+----------------------------------------------------------+
|
+---------+----------------------------------------------------------+
|
||||||
|
|
||||||
|
.. _chrono-specs:
|
||||||
|
|
||||||
|
Chrono Format Specifications
|
||||||
|
============================
|
||||||
|
|
||||||
|
Format specifications for chrono types have the following syntax:
|
||||||
|
|
||||||
|
.. productionlist:: sf
|
||||||
|
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
|
||||||
|
chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char`
|
||||||
|
conversion_spec: "%" [`modifier`] `chrono_type`
|
||||||
|
literal_char: <a character other than '{', '}' or '%'>
|
||||||
|
modifier: "E" | "O"
|
||||||
|
chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" |
|
||||||
|
: "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" |
|
||||||
|
: "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" |
|
||||||
|
: "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%"
|
||||||
|
|
||||||
|
Literal chars are copied unchanged to the output. Precision is valid only for
|
||||||
|
``std::chrono::duration`` types with a floating-point representation type.
|
||||||
|
|
||||||
|
The available presentation types (*chrono_type*) for chrono durations and time
|
||||||
|
points are:
|
||||||
|
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| Type | Meaning |
|
||||||
|
+=========+====================================================================+
|
||||||
|
| ``'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`` |
|
||||||
|
| | produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'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 |
|
||||||
|
| | locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
| ``'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 |
|
||||||
|
| | cannot be exactly represented with seconds, then the format is a |
|
||||||
|
| | decimal floating-point number with a fixed format and a precision |
|
||||||
|
| | matching that of the precision of the input (or to a microseconds |
|
||||||
|
| | precision if the conversion to floating-point decimal seconds |
|
||||||
|
| | cannot be made within 18 fractional digits). The character for the |
|
||||||
|
| | decimal point is localized according to the locale. The modified |
|
||||||
|
| | command ``%OS`` produces the locale's alternative representation. |
|
||||||
|
+---------+--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
``std::tm`` uses the system's `strftime
|
||||||
|
<https://en.cppreference.com/w/cpp/chrono/c/strftime>`_ so refer to its
|
||||||
|
documentation for details on supported conversion specifiers.
|
||||||
|
|
||||||
.. _formatexamples:
|
.. _formatexamples:
|
||||||
|
|
||||||
Format Examples
|
Format Examples
|
||||||
|
@ -391,7 +439,7 @@ Using type-specific formatting::
|
||||||
|
|
||||||
auto t = tm();
|
auto t = tm();
|
||||||
t.tm_year = 2010 - 1900;
|
t.tm_year = 2010 - 1900;
|
||||||
t.tm_mon = 6;
|
t.tm_mon = 7;
|
||||||
t.tm_mday = 4;
|
t.tm_mday = 4;
|
||||||
t.tm_hour = 12;
|
t.tm_hour = 12;
|
||||||
t.tm_min = 15;
|
t.tm_min = 15;
|
||||||
|
|
|
@ -15,7 +15,7 @@ Building the Library
|
||||||
|
|
||||||
The included `CMake build script`__ can be used to build the fmt
|
The included `CMake build script`__ can be used to build the fmt
|
||||||
library on a wide range of platforms. CMake is freely available for
|
library on a wide range of platforms. CMake is freely available for
|
||||||
download from http://www.cmake.org/download/.
|
download from https://www.cmake.org/download/.
|
||||||
|
|
||||||
__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt
|
__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt
|
||||||
|
|
||||||
|
@ -50,7 +50,15 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
|
||||||
|
|
||||||
cmake -DBUILD_SHARED_LIBS=TRUE ...
|
cmake -DBUILD_SHARED_LIBS=TRUE ...
|
||||||
|
|
||||||
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
__ https://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
|
||||||
|
|
||||||
|
|
||||||
|
To build a `static library` with position independent code (required if the main
|
||||||
|
consumer of the fmt library is a shared library i.e. a Python extension) set the
|
||||||
|
``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``::
|
||||||
|
|
||||||
|
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ...
|
||||||
|
|
||||||
|
|
||||||
Installing the Library
|
Installing the Library
|
||||||
======================
|
======================
|
||||||
|
@ -83,6 +91,49 @@ Setting up your target to use a header-only version of ``fmt`` is equally easy::
|
||||||
|
|
||||||
target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only)
|
target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only)
|
||||||
|
|
||||||
|
Usage with build2
|
||||||
|
=================
|
||||||
|
|
||||||
|
You can use `build2 <https://build2.org>`_, a dependency manager and a
|
||||||
|
build-system combined, to use ``fmt``.
|
||||||
|
|
||||||
|
Currently this package is available in these package repositories:
|
||||||
|
|
||||||
|
- **https://cppget.org/fmt/** for released and published versions.
|
||||||
|
- `The git repository with the sources of the build2 package of fmt <https://github.com/build2-packaging/fmt.git>`_
|
||||||
|
for unreleased or custom revisions of ``fmt``.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
- ``build2`` package name: ``fmt``
|
||||||
|
- Library target name : ``lib{fmt}``
|
||||||
|
|
||||||
|
For example, to make your ``build2`` project depend on ``fmt``:
|
||||||
|
|
||||||
|
- Add one of the repositories to your configurations, or in your
|
||||||
|
``repositories.manifest``, if not already there::
|
||||||
|
|
||||||
|
:
|
||||||
|
role: prerequisite
|
||||||
|
location: https://pkg.cppget.org/1/stable
|
||||||
|
|
||||||
|
- Add this package as a dependency to your ``./manifest`` file
|
||||||
|
(example for ``v7.0.x``)::
|
||||||
|
|
||||||
|
depends: fmt ~7.0.0
|
||||||
|
|
||||||
|
- Import the target and use it as a prerequisite to your own target
|
||||||
|
using `fmt` in the appropriate ``buildfile``::
|
||||||
|
|
||||||
|
import fmt = fmt%lib{fmt}
|
||||||
|
lib{mylib} : cxx{**} ... $fmt
|
||||||
|
|
||||||
|
Then build your project as usual with `b` or `bdep update`.
|
||||||
|
|
||||||
|
For ``build2`` newcomers or to get more details and use cases, you can read the
|
||||||
|
``build2``
|
||||||
|
`toolchain introduction <https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml>`_.
|
||||||
|
|
||||||
Building the Documentation
|
Building the Documentation
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
@ -130,6 +181,18 @@ The fmt port in vcpkg is kept up to date by Microsoft team members and community
|
||||||
contributors. If the version is out of date, please `create an issue or pull
|
contributors. If the version is out of date, please `create an issue or pull
|
||||||
request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
|
request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
|
||||||
|
|
||||||
|
LHelper
|
||||||
|
=======
|
||||||
|
|
||||||
|
You can download and install fmt using
|
||||||
|
`lhelper <https://github.com/franko/lhelper>`__ dependency manager::
|
||||||
|
|
||||||
|
lhelper activate <some-environment>
|
||||||
|
lhelper install fmt
|
||||||
|
|
||||||
|
All the recipes for lhelper are kept in the
|
||||||
|
`lhelper's recipe <https://github.com/franko/lhelper-recipes>`__ repository.
|
||||||
|
|
||||||
Android NDK
|
Android NDK
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -139,11 +202,11 @@ For an example of using fmt with Android NDK, see the
|
||||||
`android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_
|
`android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_
|
||||||
repository.
|
repository.
|
||||||
|
|
||||||
__ https://github.com/fmtlib/fmt/blob/master/Android.mk
|
__ https://github.com/fmtlib/fmt/blob/master/support/Android.mk
|
||||||
|
|
||||||
Homebrew
|
Homebrew
|
||||||
========
|
========
|
||||||
|
|
||||||
fmt can be installed on OS X using `Homebrew <http://brew.sh/>`_::
|
fmt can be installed on OS X using `Homebrew <https://brew.sh/>`_::
|
||||||
|
|
||||||
brew install fmt
|
brew install fmt
|
||||||
|
|
232
include/fmt/args.h
Normal file
232
include/fmt/args.h
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
// Formatting library for C++ - dynamic format arguments
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FMT_ARGS_H_
|
||||||
|
#define FMT_ARGS_H_
|
||||||
|
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||||
|
template <typename T>
|
||||||
|
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename T> const T& unwrap(const T& v) { return v; }
|
||||||
|
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
|
||||||
|
return static_cast<const T&>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
class dynamic_arg_list {
|
||||||
|
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||||
|
// templates it doesn't complain about inability to deduce single translation
|
||||||
|
// unit for placing vtable. So storage_node_base is made a fake template.
|
||||||
|
template <typename = void> struct node {
|
||||||
|
virtual ~node() = default;
|
||||||
|
std::unique_ptr<node<>> next;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> struct typed_node : node<> {
|
||||||
|
T value;
|
||||||
|
|
||||||
|
template <typename Arg>
|
||||||
|
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
|
||||||
|
: value(arg.data(), arg.size()) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<node<>> head_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T, typename Arg> const T& push(const Arg& arg) {
|
||||||
|
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||||
|
auto& value = new_node->value;
|
||||||
|
new_node->next = std::move(head_);
|
||||||
|
head_ = std::move(new_node);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
A dynamic version of `fmt::format_arg_store`.
|
||||||
|
It's equipped with a storage to potentially temporary objects which lifetimes
|
||||||
|
could be shorter than the format arguments object.
|
||||||
|
|
||||||
|
It can be implicitly converted into `~fmt::basic_format_args` for passing
|
||||||
|
into type-erased formatting functions such as `~fmt::vformat`.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename Context>
|
||||||
|
class dynamic_format_arg_store
|
||||||
|
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||||
|
// Workaround a GCC template argument substitution bug.
|
||||||
|
: public basic_format_args<Context>
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using char_type = typename Context::char_type;
|
||||||
|
|
||||||
|
template <typename T> struct need_copy {
|
||||||
|
static constexpr detail::type mapped_type =
|
||||||
|
detail::mapped_type_constant<T, Context>::value;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
value = !(detail::is_reference_wrapper<T>::value ||
|
||||||
|
std::is_same<T, basic_string_view<char_type>>::value ||
|
||||||
|
std::is_same<T, detail::std_string_view<char_type>>::value ||
|
||||||
|
(mapped_type != detail::type::cstring_type &&
|
||||||
|
mapped_type != detail::type::string_type &&
|
||||||
|
mapped_type != detail::type::custom_type))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using stored_type = conditional_t<detail::is_string<T>::value &&
|
||||||
|
!has_formatter<T, Context>::value &&
|
||||||
|
!detail::is_reference_wrapper<T>::value,
|
||||||
|
std::basic_string<char_type>, T>;
|
||||||
|
|
||||||
|
// Storage of basic_format_arg must be contiguous.
|
||||||
|
std::vector<basic_format_arg<Context>> data_;
|
||||||
|
std::vector<detail::named_arg_info<char_type>> named_info_;
|
||||||
|
|
||||||
|
// Storage of arguments not fitting into basic_format_arg must grow
|
||||||
|
// without relocation because items in data_ refer to it.
|
||||||
|
detail::dynamic_arg_list dynamic_args_;
|
||||||
|
|
||||||
|
friend class basic_format_args<Context>;
|
||||||
|
|
||||||
|
unsigned long long get_types() const {
|
||||||
|
return detail::is_unpacked_bit | data_.size() |
|
||||||
|
(named_info_.empty()
|
||||||
|
? 0ULL
|
||||||
|
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
const basic_format_arg<Context>* data() const {
|
||||||
|
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void emplace_arg(const T& arg) {
|
||||||
|
data_.emplace_back(detail::make_arg<Context>(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||||
|
if (named_info_.empty()) {
|
||||||
|
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
||||||
|
data_.insert(data_.begin(), {zero_ptr, 0});
|
||||||
|
}
|
||||||
|
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
||||||
|
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||||
|
data->pop_back();
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||||
|
guard{&data_, pop_one};
|
||||||
|
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||||
|
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
||||||
|
guard.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Adds an argument into the dynamic store for later passing to a formatting
|
||||||
|
function.
|
||||||
|
|
||||||
|
Note that custom types and string types (but not string views) are copied
|
||||||
|
into the store dynamically allocating memory if necessary.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
|
store.push_back(42);
|
||||||
|
store.push_back("abc");
|
||||||
|
store.push_back(1.5f);
|
||||||
|
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename T> void push_back(const T& arg) {
|
||||||
|
if (detail::const_check(need_copy<T>::value))
|
||||||
|
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
||||||
|
else
|
||||||
|
emplace_arg(detail::unwrap(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Adds a reference to the argument into the dynamic store for later passing to
|
||||||
|
a formatting function.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
|
char band[] = "Rolling Stones";
|
||||||
|
store.push_back(std::cref(band));
|
||||||
|
band[9] = 'c'; // Changing str affects the output.
|
||||||
|
std::string result = fmt::vformat("{}", store);
|
||||||
|
// result == "Rolling Scones"
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename T> void push_back(std::reference_wrapper<T> arg) {
|
||||||
|
static_assert(
|
||||||
|
need_copy<T>::value,
|
||||||
|
"objects of built-in types and string views are always copied");
|
||||||
|
emplace_arg(arg.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Adds named argument into the dynamic store for later passing to a formatting
|
||||||
|
function. ``std::reference_wrapper`` is supported to avoid copying of the
|
||||||
|
argument. The name is always copied into the store.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
void push_back(const detail::named_arg<char_type, T>& arg) {
|
||||||
|
const char_type* arg_name =
|
||||||
|
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||||
|
if (detail::const_check(need_copy<T>::value)) {
|
||||||
|
emplace_arg(
|
||||||
|
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
||||||
|
} else {
|
||||||
|
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Erase all elements from the store */
|
||||||
|
void clear() {
|
||||||
|
data_.clear();
|
||||||
|
named_info_.clear();
|
||||||
|
dynamic_args_ = detail::dynamic_arg_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Reserves space to store at least *new_cap* arguments including
|
||||||
|
*new_cap_named* named arguments.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||||
|
FMT_ASSERT(new_cap >= new_cap_named,
|
||||||
|
"Set of arguments includes set of named arguments");
|
||||||
|
data_.reserve(new_cap);
|
||||||
|
named_info_.reserve(new_cap_named);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // FMT_ARGS_H_
|
|
@ -8,13 +8,13 @@
|
||||||
#ifndef FMT_CHRONO_H_
|
#ifndef FMT_CHRONO_H_
|
||||||
#define FMT_CHRONO_H_
|
#define FMT_CHRONO_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "locale.h"
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
@ -72,43 +72,27 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||||
static_assert(F::is_integer, "From must be integral");
|
static_assert(F::is_integer, "From must be integral");
|
||||||
static_assert(T::is_integer, "To must be integral");
|
static_assert(T::is_integer, "To must be integral");
|
||||||
|
|
||||||
if (F::is_signed && !T::is_signed) {
|
if (detail::const_check(F::is_signed && !T::is_signed)) {
|
||||||
// From may be negative, not allowed!
|
// From may be negative, not allowed!
|
||||||
if (fmt::detail::is_negative(from)) {
|
if (fmt::detail::is_negative(from)) {
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// From is positive. Can it always fit in To?
|
// From is positive. Can it always fit in To?
|
||||||
if (F::digits <= T::digits) {
|
if (F::digits > T::digits &&
|
||||||
// yes, From always fits in To.
|
from > static_cast<From>(detail::max_value<To>())) {
|
||||||
} else {
|
ec = 1;
|
||||||
// from may not fit in To, we have to do a dynamic check
|
return {};
|
||||||
if (from > static_cast<From>((T::max)())) {
|
|
||||||
ec = 1;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!F::is_signed && T::is_signed) {
|
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
|
||||||
// can from be held in To?
|
from > static_cast<From>(detail::max_value<To>())) {
|
||||||
if (F::digits < T::digits) {
|
ec = 1;
|
||||||
// yes, From always fits in To.
|
return {};
|
||||||
} else {
|
|
||||||
// from may not fit in To, we have to do a dynamic check
|
|
||||||
if (from > static_cast<From>((T::max)())) {
|
|
||||||
// outside range.
|
|
||||||
ec = 1;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return static_cast<To>(from); // Lossless conversion.
|
||||||
// reaching here means all is ok for lossless conversion.
|
}
|
||||||
return static_cast<To>(from);
|
|
||||||
|
|
||||||
} // function
|
|
||||||
|
|
||||||
template <typename To, typename From,
|
template <typename To, typename From,
|
||||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||||
|
@ -190,11 +174,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||||
// safe conversion to IntermediateRep
|
// safe conversion to IntermediateRep
|
||||||
IntermediateRep count =
|
IntermediateRep count =
|
||||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
||||||
if (ec) {
|
if (ec) return {};
|
||||||
return {};
|
|
||||||
}
|
|
||||||
// multiply with Factor::num without overflow or underflow
|
// multiply with Factor::num without overflow or underflow
|
||||||
if (Factor::num != 1) {
|
if (detail::const_check(Factor::num != 1)) {
|
||||||
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
|
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
|
||||||
if (count > max1) {
|
if (count > max1) {
|
||||||
ec = 1;
|
ec = 1;
|
||||||
|
@ -209,17 +191,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||||
count *= Factor::num;
|
count *= Factor::num;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this can't go wrong, right? den>0 is checked earlier.
|
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
|
||||||
if (Factor::den != 1) {
|
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
|
||||||
count /= Factor::den;
|
return ec ? To() : To(tocount);
|
||||||
}
|
|
||||||
// convert to the to type, safely
|
|
||||||
using ToRep = typename To::rep;
|
|
||||||
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
|
|
||||||
if (ec) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return To{tocount};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -308,13 +282,89 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||||
#define FMT_NOMACRO
|
#define FMT_NOMACRO
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
template <typename T = void> struct null {};
|
||||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||||
inline null<> localtime_s(...) { return null<>(); }
|
inline null<> localtime_s(...) { return null<>(); }
|
||||||
inline null<> gmtime_r(...) { return null<>(); }
|
inline null<> gmtime_r(...) { return null<>(); }
|
||||||
inline null<> gmtime_s(...) { return null<>(); }
|
inline null<> gmtime_s(...) { return null<>(); }
|
||||||
|
|
||||||
|
inline auto do_write(const std::tm& time, const std::locale& loc, char format,
|
||||||
|
char modifier) -> std::string {
|
||||||
|
auto&& os = std::ostringstream();
|
||||||
|
os.imbue(loc);
|
||||||
|
using iterator = std::ostreambuf_iterator<char>;
|
||||||
|
const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
|
||||||
|
auto end = facet.put(os, os, ' ', &time, format, modifier);
|
||||||
|
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
||||||
|
auto str = os.str();
|
||||||
|
if (!detail::is_utf8() || loc == std::locale::classic()) return str;
|
||||||
|
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
|
||||||
|
// gcc-4.
|
||||||
|
#if FMT_MSC_VER != 0 || \
|
||||||
|
(defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
|
||||||
|
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
|
||||||
|
// and newer.
|
||||||
|
using code_unit = wchar_t;
|
||||||
|
#else
|
||||||
|
using code_unit = char32_t;
|
||||||
|
#endif
|
||||||
|
auto& f = std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
|
||||||
|
auto mb = std::mbstate_t();
|
||||||
|
const char* from_next = nullptr;
|
||||||
|
code_unit* to_next = nullptr;
|
||||||
|
constexpr size_t buf_size = 32;
|
||||||
|
code_unit buf[buf_size] = {};
|
||||||
|
auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
|
||||||
|
buf + buf_size, to_next);
|
||||||
|
if (result != std::codecvt_base::ok)
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
str.clear();
|
||||||
|
for (code_unit* p = buf; p != to_next; ++p) {
|
||||||
|
uint32_t c = static_cast<uint32_t>(*p);
|
||||||
|
if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
|
||||||
|
// surrogate pair
|
||||||
|
++p;
|
||||||
|
if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
}
|
||||||
|
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
|
||||||
|
}
|
||||||
|
if (c < 0x80) {
|
||||||
|
str.push_back(static_cast<char>(c));
|
||||||
|
} else if (c < 0x800) {
|
||||||
|
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
|
||||||
|
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else if (c >= 0x10000 && c <= 0x10ffff) {
|
||||||
|
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else {
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||||
|
char format, char modifier = 0) -> OutputIt {
|
||||||
|
auto str = do_write(time, loc, format, modifier);
|
||||||
|
return std::copy(str.begin(), str.end(), out);
|
||||||
|
}
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// Thread-safe replacement for std::localtime
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts given time since epoch as ``std::time_t`` value into calendar time,
|
||||||
|
expressed in local time. Unlike ``std::localtime``, this function is
|
||||||
|
thread-safe on most platforms.
|
||||||
|
*/
|
||||||
inline std::tm localtime(std::time_t time) {
|
inline std::tm localtime(std::time_t time) {
|
||||||
struct dispatcher {
|
struct dispatcher {
|
||||||
std::time_t time_;
|
std::time_t time_;
|
||||||
|
@ -351,7 +401,16 @@ inline std::tm localtime(std::time_t time) {
|
||||||
return lt.tm_;
|
return lt.tm_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe replacement for std::gmtime
|
inline std::tm localtime(
|
||||||
|
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||||
|
return localtime(std::chrono::system_clock::to_time_t(time_point));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts given time since epoch as ``std::time_t`` value into calendar time,
|
||||||
|
expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
|
||||||
|
function is thread-safe on most platforms.
|
||||||
|
*/
|
||||||
inline std::tm gmtime(std::time_t time) {
|
inline std::tm gmtime(std::time_t time) {
|
||||||
struct dispatcher {
|
struct dispatcher {
|
||||||
std::time_t time_;
|
std::time_t time_;
|
||||||
|
@ -387,33 +446,89 @@ inline std::tm gmtime(std::time_t time) {
|
||||||
return gt.tm_;
|
return gt.tm_;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
inline std::tm gmtime(
|
||||||
|
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||||
|
return gmtime(std::chrono::system_clock::to_time_t(time_point));
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
inline size_t strftime(char* str, size_t count, const char* format,
|
inline size_t strftime(char* str, size_t count, const char* format,
|
||||||
const std::tm* time) {
|
const std::tm* time) {
|
||||||
return std::strftime(str, count, format, time);
|
// Assign to a pointer to suppress GCCs -Wformat-nonliteral
|
||||||
|
// First assign the nullptr to suppress -Wsuggest-attribute=format
|
||||||
|
std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
|
||||||
|
nullptr;
|
||||||
|
strftime = std::strftime;
|
||||||
|
return strftime(str, count, format, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
||||||
const std::tm* time) {
|
const std::tm* time) {
|
||||||
return std::wcsftime(str, count, format, time);
|
// See above
|
||||||
|
std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
|
||||||
|
const std::tm*) = nullptr;
|
||||||
|
wcsftime = std::wcsftime;
|
||||||
|
return wcsftime(str, count, format, time);
|
||||||
}
|
}
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <typename Char> struct formatter<std::tm, Char> {
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
template <typename Char, typename Duration>
|
||||||
|
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||||
|
Char> : formatter<std::tm, Char> {
|
||||||
|
FMT_CONSTEXPR formatter() {
|
||||||
|
this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)};
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
if (it != ctx.end() && *it == ':') ++it;
|
if (it != ctx.end() && *it == ':') ++it;
|
||||||
auto end = it;
|
auto end = it;
|
||||||
while (end != ctx.end() && *end != '}') ++end;
|
while (end != ctx.end() && *end != '}') ++end;
|
||||||
tm_format.reserve(detail::to_unsigned(end - it + 1));
|
if (end != it) this->specs = {it, detail::to_unsigned(end - it)};
|
||||||
tm_format.append(it, end);
|
|
||||||
tm_format.push_back('\0');
|
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(std::chrono::time_point<std::chrono::system_clock> val,
|
||||||
|
FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
|
std::tm time = localtime(val);
|
||||||
|
return formatter<std::tm, Char>::format(time, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-',
|
||||||
|
'%', 'd', ' ', '%', 'H', ':',
|
||||||
|
'%', 'M', ':', '%', 'S'};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename Duration>
|
||||||
|
constexpr Char
|
||||||
|
formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||||
|
Char>::default_specs[];
|
||||||
|
|
||||||
|
template <typename Char> struct formatter<std::tm, Char> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
auto it = ctx.begin();
|
||||||
|
if (it != ctx.end() && *it == ':') ++it;
|
||||||
|
auto end = it;
|
||||||
|
while (end != ctx.end() && *end != '}') ++end;
|
||||||
|
specs = {it, detail::to_unsigned(end - it)};
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const std::tm& tm, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
basic_memory_buffer<Char> tm_format;
|
||||||
|
tm_format.append(specs.begin(), specs.end());
|
||||||
|
// By appending an extra space we can distinguish an empty result that
|
||||||
|
// indicates insufficient buffer size from a guaranteed non-empty result
|
||||||
|
// https://github.com/fmtlib/fmt/issues/2238
|
||||||
|
tm_format.push_back(' ');
|
||||||
|
tm_format.push_back('\0');
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
size_t start = buf.size();
|
size_t start = buf.size();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -423,49 +538,40 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||||
buf.resize(start + count);
|
buf.resize(start + count);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (size >= tm_format.size() * 256) {
|
|
||||||
// If the buffer is 256 times larger than the format string, assume
|
|
||||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
|
||||||
// better way to distinguish the two cases:
|
|
||||||
// https://github.com/fmtlib/fmt/issues/367
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const size_t MIN_GROWTH = 10;
|
const size_t MIN_GROWTH = 10;
|
||||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||||
}
|
}
|
||||||
return std::copy(buf.begin(), buf.end(), ctx.out());
|
// Remove the extra space.
|
||||||
|
return std::copy(buf.begin(), buf.end() - 1, ctx.out());
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_memory_buffer<Char> tm_format;
|
basic_string_view<Char> specs;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
template <typename Period> FMT_CONSTEXPR const char* get_units() {
|
|
||||||
|
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
|
||||||
|
if (std::is_same<Period, std::atto>::value) return "as";
|
||||||
|
if (std::is_same<Period, std::femto>::value) return "fs";
|
||||||
|
if (std::is_same<Period, std::pico>::value) return "ps";
|
||||||
|
if (std::is_same<Period, std::nano>::value) return "ns";
|
||||||
|
if (std::is_same<Period, std::micro>::value) return "µs";
|
||||||
|
if (std::is_same<Period, std::milli>::value) return "ms";
|
||||||
|
if (std::is_same<Period, std::centi>::value) return "cs";
|
||||||
|
if (std::is_same<Period, std::deci>::value) return "ds";
|
||||||
|
if (std::is_same<Period, std::ratio<1>>::value) return "s";
|
||||||
|
if (std::is_same<Period, std::deca>::value) return "das";
|
||||||
|
if (std::is_same<Period, std::hecto>::value) return "hs";
|
||||||
|
if (std::is_same<Period, std::kilo>::value) return "ks";
|
||||||
|
if (std::is_same<Period, std::mega>::value) return "Ms";
|
||||||
|
if (std::is_same<Period, std::giga>::value) return "Gs";
|
||||||
|
if (std::is_same<Period, std::tera>::value) return "Ts";
|
||||||
|
if (std::is_same<Period, std::peta>::value) return "Ps";
|
||||||
|
if (std::is_same<Period, std::exa>::value) return "Es";
|
||||||
|
if (std::is_same<Period, std::ratio<60>>::value) return "m";
|
||||||
|
if (std::is_same<Period, std::ratio<3600>>::value) return "h";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
|
|
||||||
return "m";
|
|
||||||
}
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
|
|
||||||
return "h";
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class numeric_system {
|
enum class numeric_system {
|
||||||
standard,
|
standard,
|
||||||
|
@ -631,33 +737,50 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct chrono_format_checker {
|
template <typename Derived> struct null_chrono_spec_handler {
|
||||||
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); }
|
FMT_CONSTEXPR void unsupported() {
|
||||||
|
static_cast<Derived*>(this)->unsupported();
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_full_month() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_us_date() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_iso_date() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_iso_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_am_pm() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_duration_value() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_utc_offset() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Char> void on_text(const Char*, const Char*) {}
|
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
||||||
FMT_NORETURN void on_abbr_weekday() { report_no_date(); }
|
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
|
||||||
FMT_NORETURN void on_full_weekday() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); }
|
template <typename Char>
|
||||||
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
||||||
FMT_NORETURN void on_abbr_month() { report_no_date(); }
|
FMT_CONSTEXPR void on_24_hour(numeric_system) {}
|
||||||
FMT_NORETURN void on_full_month() { report_no_date(); }
|
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
|
||||||
void on_24_hour(numeric_system) {}
|
FMT_CONSTEXPR void on_minute(numeric_system) {}
|
||||||
void on_12_hour(numeric_system) {}
|
FMT_CONSTEXPR void on_second(numeric_system) {}
|
||||||
void on_minute(numeric_system) {}
|
FMT_CONSTEXPR void on_12_hour_time() {}
|
||||||
void on_second(numeric_system) {}
|
FMT_CONSTEXPR void on_24_hour_time() {}
|
||||||
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_iso_time() {}
|
||||||
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_am_pm() {}
|
||||||
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_duration_value() {}
|
||||||
FMT_NORETURN void on_us_date() { report_no_date(); }
|
FMT_CONSTEXPR void on_duration_unit() {}
|
||||||
FMT_NORETURN void on_iso_date() { report_no_date(); }
|
|
||||||
void on_12_hour_time() {}
|
|
||||||
void on_24_hour_time() {}
|
|
||||||
void on_iso_time() {}
|
|
||||||
void on_am_pm() {}
|
|
||||||
void on_duration_value() {}
|
|
||||||
void on_duration_unit() {}
|
|
||||||
FMT_NORETURN void on_utc_offset() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_tz_name() { report_no_date(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
|
@ -681,7 +804,8 @@ inline bool isfinite(T value) {
|
||||||
// Converts value to int and checks that it's in the range [0, upper).
|
// Converts value to int and checks that it's in the range [0, upper).
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
inline int to_nonnegative_int(T value, int upper) {
|
inline int to_nonnegative_int(T value, int upper) {
|
||||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
|
FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
|
||||||
|
"invalid value");
|
||||||
(void)upper;
|
(void)upper;
|
||||||
return static_cast<int>(value);
|
return static_cast<int>(value);
|
||||||
}
|
}
|
||||||
|
@ -759,15 +883,21 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
||||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
|
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename Rep, typename OutputIt>
|
template <typename Char, typename Rep, typename OutputIt,
|
||||||
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||||
const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0};
|
OutputIt format_duration_value(OutputIt out, Rep val, int) {
|
||||||
if (precision >= 0) return format_to(out, pr_f, val, precision);
|
return write<Char>(out, val);
|
||||||
const Char fp_f[] = {'{', ':', 'g', '}', 0};
|
|
||||||
const Char format[] = {'{', '}', 0};
|
|
||||||
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
|
|
||||||
val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char, typename Rep, typename OutputIt,
|
||||||
|
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
|
||||||
|
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||||
|
auto specs = basic_format_specs<Char>();
|
||||||
|
specs.precision = precision;
|
||||||
|
specs.type = precision > 0 ? 'f' : 'g';
|
||||||
|
return write<Char>(out, val, specs);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
template <typename Char, typename OutputIt>
|
||||||
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
||||||
return std::copy(unit.begin(), unit.end(), out);
|
return std::copy(unit.begin(), unit.end(), out);
|
||||||
|
@ -785,10 +915,15 @@ template <typename Char, typename Period, typename OutputIt>
|
||||||
OutputIt format_duration_unit(OutputIt out) {
|
OutputIt format_duration_unit(OutputIt out) {
|
||||||
if (const char* unit = get_units<Period>())
|
if (const char* unit = get_units<Period>())
|
||||||
return copy_unit(string_view(unit), out, Char());
|
return copy_unit(string_view(unit), out, Char());
|
||||||
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
|
*out++ = '[';
|
||||||
if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num);
|
out = write<Char>(out, Period::num);
|
||||||
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
|
if (const_check(Period::den != 1)) {
|
||||||
return format_to(out, num_def_f, Period::num, Period::den);
|
*out++ = '/';
|
||||||
|
out = write<Char>(out, Period::den);
|
||||||
|
}
|
||||||
|
*out++ = ']';
|
||||||
|
*out++ = 's';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext, typename OutputIt, typename Rep,
|
template <typename FormatContext, typename OutputIt, typename Rep,
|
||||||
|
@ -797,6 +932,7 @@ struct chrono_formatter {
|
||||||
FormatContext& context;
|
FormatContext& context;
|
||||||
OutputIt out;
|
OutputIt out;
|
||||||
int precision;
|
int precision;
|
||||||
|
bool localized = false;
|
||||||
// rep is unsigned to avoid overflow.
|
// rep is unsigned to avoid overflow.
|
||||||
using rep =
|
using rep =
|
||||||
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
|
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
|
||||||
|
@ -891,13 +1027,9 @@ struct chrono_formatter {
|
||||||
|
|
||||||
void format_localized(const tm& time, char format, char modifier = 0) {
|
void format_localized(const tm& time, char format, char modifier = 0) {
|
||||||
if (isnan(val)) return write_nan();
|
if (isnan(val)) return write_nan();
|
||||||
auto locale = context.locale().template get<std::locale>();
|
const auto& loc = localized ? context.locale().template get<std::locale>()
|
||||||
auto& facet = std::use_facet<std::time_put<char_type>>(locale);
|
: std::locale::classic();
|
||||||
std::basic_ostringstream<char_type> os;
|
out = detail::write(out, time, loc, format, modifier);
|
||||||
os.imbue(locale);
|
|
||||||
facet.put(os, os, ' ', &time, format, modifier);
|
|
||||||
auto str = os.str();
|
|
||||||
std::copy(str.begin(), str.end(), out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_text(const char_type* begin, const char_type* end) {
|
void on_text(const char_type* begin, const char_type* end) {
|
||||||
|
@ -1010,17 +1142,59 @@ struct chrono_formatter {
|
||||||
out = format_duration_unit<char_type, Period>(out);
|
out = format_duration_unit<char_type, Period>(out);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace detail
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
|
||||||
|
using weekday = std::chrono::weekday;
|
||||||
|
#else
|
||||||
|
// A fallback version of weekday.
|
||||||
|
class weekday {
|
||||||
|
private:
|
||||||
|
unsigned char value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
weekday() = default;
|
||||||
|
explicit constexpr weekday(unsigned wd) noexcept
|
||||||
|
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
|
||||||
|
constexpr unsigned c_encoding() const noexcept { return value; }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// A rudimentary weekday formatter.
|
||||||
|
template <> struct formatter<weekday> {
|
||||||
|
private:
|
||||||
|
bool localized = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
|
auto begin = ctx.begin(), end = ctx.end();
|
||||||
|
if (begin != end && *begin == 'L') {
|
||||||
|
++begin;
|
||||||
|
localized = true;
|
||||||
|
}
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
|
auto time = std::tm();
|
||||||
|
time.tm_wday = static_cast<int>(wd.c_encoding());
|
||||||
|
const auto& loc = localized ? ctx.locale().template get<std::locale>()
|
||||||
|
: std::locale::classic();
|
||||||
|
return detail::write(ctx.out(), time, loc, 'a');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Rep, typename Period, typename Char>
|
template <typename Rep, typename Period, typename Char>
|
||||||
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
private:
|
private:
|
||||||
basic_format_specs<Char> specs;
|
basic_format_specs<Char> specs;
|
||||||
int precision;
|
int precision = -1;
|
||||||
using arg_ref_type = detail::arg_ref<Char>;
|
using arg_ref_type = detail::arg_ref<Char>;
|
||||||
arg_ref_type width_ref;
|
arg_ref_type width_ref;
|
||||||
arg_ref_type precision_ref;
|
arg_ref_type precision_ref;
|
||||||
mutable basic_string_view<Char> format_str;
|
bool localized = false;
|
||||||
|
basic_string_view<Char> format_str;
|
||||||
using duration = std::chrono::duration<Rep, Period>;
|
using duration = std::chrono::duration<Rep, Period>;
|
||||||
|
|
||||||
struct spec_handler {
|
struct spec_handler {
|
||||||
|
@ -1043,17 +1217,21 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
|
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
|
||||||
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; }
|
FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
|
||||||
void on_align(align_t align) { f.specs.align = align; }
|
f.specs.fill = fill;
|
||||||
void on_width(int width) { f.specs.width = width; }
|
}
|
||||||
void on_precision(int _precision) { f.precision = _precision; }
|
FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
|
||||||
void end_precision() {}
|
FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
|
||||||
|
FMT_CONSTEXPR void on_precision(int _precision) {
|
||||||
|
f.precision = _precision;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR void end_precision() {}
|
||||||
|
|
||||||
template <typename Id> void on_dynamic_width(Id arg_id) {
|
template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
|
||||||
f.width_ref = make_arg_ref(arg_id);
|
f.width_ref = make_arg_ref(arg_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Id> void on_dynamic_precision(Id arg_id) {
|
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
|
||||||
f.precision_ref = make_arg_ref(arg_id);
|
f.precision_ref = make_arg_ref(arg_id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1078,13 +1256,15 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
else
|
else
|
||||||
handler.on_error("precision not allowed for this argument type");
|
handler.on_error("precision not allowed for this argument type");
|
||||||
}
|
}
|
||||||
|
if (begin != end && *begin == 'L') {
|
||||||
|
++begin;
|
||||||
|
localized = true;
|
||||||
|
}
|
||||||
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
|
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
|
||||||
return {begin, end};
|
return {begin, end};
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
formatter() : precision(-1) {}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||||
-> decltype(ctx.begin()) {
|
-> decltype(ctx.begin()) {
|
||||||
auto range = do_parse(ctx);
|
auto range = do_parse(ctx);
|
||||||
|
@ -1094,30 +1274,35 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(const duration& d, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
auto specs_copy = specs;
|
||||||
|
auto precision_copy = precision;
|
||||||
auto begin = format_str.begin(), end = format_str.end();
|
auto begin = format_str.begin(), end = format_str.end();
|
||||||
// As a possible future optimization, we could avoid extra copying if width
|
// As a possible future optimization, we could avoid extra copying if width
|
||||||
// is not specified.
|
// is not specified.
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
auto out = std::back_inserter(buf);
|
auto out = std::back_inserter(buf);
|
||||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref,
|
detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
|
||||||
ctx);
|
width_ref, ctx);
|
||||||
detail::handle_dynamic_spec<detail::precision_checker>(precision,
|
detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
|
||||||
precision_ref, ctx);
|
precision_ref, ctx);
|
||||||
if (begin == end || *begin == '}') {
|
if (begin == end || *begin == '}') {
|
||||||
out = detail::format_duration_value<Char>(out, d.count(), precision);
|
out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
|
||||||
detail::format_duration_unit<Char, Period>(out);
|
detail::format_duration_unit<Char, Period>(out);
|
||||||
} else {
|
} else {
|
||||||
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
||||||
ctx, out, d);
|
ctx, out, d);
|
||||||
f.precision = precision;
|
f.precision = precision_copy;
|
||||||
parse_chrono_format(begin, end, f);
|
f.localized = localized;
|
||||||
|
detail::parse_chrono_format(begin, end, f);
|
||||||
}
|
}
|
||||||
return detail::write(
|
return detail::write(
|
||||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_CHRONO_H_
|
#endif // FMT_CHRONO_H_
|
||||||
|
|
|
@ -10,7 +10,15 @@
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
|
// __declspec(deprecated) is broken in some MSVC versions.
|
||||||
|
#if FMT_MSC_VER
|
||||||
|
# define FMT_DEPRECATED_NONMSVC
|
||||||
|
#else
|
||||||
|
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
enum class color : uint32_t {
|
enum class color : uint32_t {
|
||||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||||
|
@ -198,7 +206,7 @@ struct rgb {
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -221,9 +229,10 @@ struct color_type {
|
||||||
uint32_t rgb_color;
|
uint32_t rgb_color;
|
||||||
} value;
|
} value;
|
||||||
};
|
};
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
// Experimental text formatting support.
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
/** A text style consisting of foreground and background colors and emphasis. */
|
||||||
class text_style {
|
class text_style {
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||||
|
@ -260,33 +269,14 @@ class text_style {
|
||||||
return lhs |= rhs;
|
return lhs |= rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
|
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
|
||||||
if (!set_foreground_color) {
|
const text_style& rhs) {
|
||||||
set_foreground_color = rhs.set_foreground_color;
|
return and_assign(rhs);
|
||||||
foreground_color = rhs.foreground_color;
|
|
||||||
} else if (rhs.set_foreground_color) {
|
|
||||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
|
||||||
FMT_THROW(format_error("can't AND a terminal color"));
|
|
||||||
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!set_background_color) {
|
|
||||||
set_background_color = rhs.set_background_color;
|
|
||||||
background_color = rhs.background_color;
|
|
||||||
} else if (rhs.set_background_color) {
|
|
||||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
|
||||||
FMT_THROW(format_error("can't AND a terminal color"));
|
|
||||||
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
|
|
||||||
static_cast<uint8_t>(rhs.ems));
|
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
|
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
|
||||||
const text_style& rhs) {
|
operator&(text_style lhs, const text_style& rhs) {
|
||||||
return lhs &= rhs;
|
return lhs.and_assign(rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||||
|
@ -326,8 +316,34 @@ class text_style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
|
||||||
|
if (!set_foreground_color) {
|
||||||
|
set_foreground_color = rhs.set_foreground_color;
|
||||||
|
foreground_color = rhs.foreground_color;
|
||||||
|
} else if (rhs.set_foreground_color) {
|
||||||
|
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||||
|
FMT_THROW(format_error("can't AND a terminal color"));
|
||||||
|
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!set_background_color) {
|
||||||
|
set_background_color = rhs.set_background_color;
|
||||||
|
background_color = rhs.background_color;
|
||||||
|
} else if (rhs.set_background_color) {
|
||||||
|
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||||
|
FMT_THROW(format_error("can't AND a terminal color"));
|
||||||
|
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
|
||||||
|
static_cast<uint8_t>(rhs.ems));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||||
FMT_NOEXCEPT;
|
FMT_NOEXCEPT;
|
||||||
|
|
||||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||||
FMT_NOEXCEPT;
|
FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
@ -338,19 +354,22 @@ class text_style {
|
||||||
emphasis ems;
|
emphasis ems;
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
/** Creates a text style from the foreground (text) color. */
|
||||||
return text_style(/*is_foreground=*/true, foreground);
|
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||||
|
return text_style(true, foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
/** Creates a text style from the background color. */
|
||||||
return text_style(/*is_foreground=*/false, background);
|
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||||
|
return text_style(false, background);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
|
||||||
|
emphasis rhs) FMT_NOEXCEPT {
|
||||||
return text_style(lhs) | rhs;
|
return text_style(lhs) | rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
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,
|
||||||
|
@ -358,7 +377,7 @@ template <typename Char> struct ansi_color_escape {
|
||||||
// If we have a terminal color, we need to output another escape code
|
// If we have a terminal color, we need to output another escape code
|
||||||
// sequence.
|
// sequence.
|
||||||
if (!text_color.is_rgb) {
|
if (!text_color.is_rgb) {
|
||||||
bool is_background = esc == detail::data::background_color;
|
bool is_background = esc == string_view("\x1b[48;2;");
|
||||||
uint32_t value = text_color.value.term_color;
|
uint32_t value = text_color.value.term_color;
|
||||||
// Background ASCII codes are the same as the foreground ones but with
|
// Background ASCII codes are the same as the foreground ones but with
|
||||||
// 10 more.
|
// 10 more.
|
||||||
|
@ -411,7 +430,7 @@ template <typename Char> struct ansi_color_escape {
|
||||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||||
|
|
||||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
|
||||||
return buffer + std::char_traits<Char>::length(buffer);
|
return buffer + std::char_traits<Char>::length(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,13 +449,13 @@ template <typename Char> struct ansi_color_escape {
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||||
detail::color_type foreground) FMT_NOEXCEPT {
|
detail::color_type foreground) FMT_NOEXCEPT {
|
||||||
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
|
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||||
detail::color_type background) FMT_NOEXCEPT {
|
detail::color_type background) FMT_NOEXCEPT {
|
||||||
return ansi_color_escape<Char>(background, detail::data::background_color);
|
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
|
@ -455,24 +474,23 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||||
fputs(detail::data::reset_color, stream);
|
fputs("\x1b[0m", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||||
fputs(detail::data::wreset_color, stream);
|
fputs(L"\x1b[0m", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||||
const char* begin = data::reset_color;
|
auto reset_color = string_view("\x1b[0m");
|
||||||
const char* end = begin + sizeof(data::reset_color) - 1;
|
buffer.append(reset_color.begin(), reset_color.end());
|
||||||
buffer.append(begin, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||||
basic_string_view<Char> format_str,
|
basic_string_view<Char> format_str,
|
||||||
basic_format_args<buffer_context<Char>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
bool has_style = false;
|
bool has_style = false;
|
||||||
if (ts.has_emphasis()) {
|
if (ts.has_emphasis()) {
|
||||||
has_style = true;
|
has_style = true;
|
||||||
|
@ -492,11 +510,12 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||||
detail::vformat_to(buf, format_str, args);
|
detail::vformat_to(buf, format_str, args);
|
||||||
if (has_style) detail::reset_color<Char>(buf);
|
if (has_style) detail::reset_color<Char>(buf);
|
||||||
}
|
}
|
||||||
} // namespace detail
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||||
basic_format_args<buffer_context<Char>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||||
buf.push_back(Char(0));
|
buf.push_back(Char(0));
|
||||||
|
@ -504,28 +523,34 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
\rst
|
||||||
Formats a string and prints it to the specified file stream using ANSI
|
Formats a string and prints it to the specified file stream using ANSI
|
||||||
escape sequences to specify text formatting.
|
escape sequences to specify text formatting.
|
||||||
Example:
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||||
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
detail::check_format_string<Args...>(format_str);
|
vprint(f, ts, format_str,
|
||||||
using context = buffer_context<char_t<S>>;
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
format_arg_store<context, Args...> as{args...};
|
|
||||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
\rst
|
||||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||||
specify text formatting.
|
specify text formatting.
|
||||||
Example:
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||||
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
|
@ -557,10 +582,46 @@ inline std::basic_string<Char> vformat(
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
return vformat(ts, to_string_view(format_str),
|
return fmt::vformat(ts, to_string_view(format_str),
|
||||||
detail::make_args_checked<Args...>(format_str, args...));
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats a string with the given text_style and writes the output to ``out``.
|
||||||
|
*/
|
||||||
|
template <typename OutputIt, typename Char,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||||
|
OutputIt vformat_to(
|
||||||
|
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
|
detail::vformat_to(buf, ts, format_str, args);
|
||||||
|
return detail::get_iterator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Formats arguments with the given text_style, writes the result to the output
|
||||||
|
iterator ``out`` and returns the iterator past the end of the output range.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
std::vector<char> out;
|
||||||
|
fmt::format_to(std::back_inserter(out),
|
||||||
|
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
|
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||||
|
detail::is_string<S>::value>
|
||||||
|
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||||
|
Args&&... args) ->
|
||||||
|
typename std::enable_if<enable, OutputIt>::type {
|
||||||
|
return vformat_to(out, ts, to_string_view(format_str),
|
||||||
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_COLOR_H_
|
#endif // FMT_COLOR_H_
|
||||||
|
|
|
@ -8,13 +8,135 @@
|
||||||
#ifndef FMT_COMPILE_H_
|
#ifndef FMT_COMPILE_H_
|
||||||
#define FMT_COMPILE_H_
|
#define FMT_COMPILE_H_
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
|
// An output iterator that counts the number of objects written to it and
|
||||||
|
// discards them.
|
||||||
|
class counting_iterator {
|
||||||
|
private:
|
||||||
|
size_t count_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator_category = std::output_iterator_tag;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using pointer = void;
|
||||||
|
using reference = void;
|
||||||
|
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
|
||||||
|
|
||||||
|
struct value_type {
|
||||||
|
template <typename T> void operator=(const T&) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
counting_iterator() : count_(0) {}
|
||||||
|
|
||||||
|
size_t count() const { return count_; }
|
||||||
|
|
||||||
|
counting_iterator& operator++() {
|
||||||
|
++count_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
counting_iterator operator++(int) {
|
||||||
|
auto it = *this;
|
||||||
|
++*this;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend counting_iterator operator+(counting_iterator it, difference_type n) {
|
||||||
|
it.count_ += static_cast<size_t>(n);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator*() const { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename InputIt>
|
||||||
|
inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||||
|
counting_iterator it) {
|
||||||
|
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;
|
||||||
|
using _Unchecked_type =
|
||||||
|
truncating_iterator_base; // Mark iterator as checked.
|
||||||
|
|
||||||
|
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 {};
|
||||||
|
|
||||||
|
@ -34,341 +156,36 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
|
#ifdef __cpp_if_constexpr
|
||||||
|
# define FMT_COMPILE(s) \
|
||||||
|
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
|
||||||
|
#else
|
||||||
|
# define FMT_COMPILE(s) FMT_STRING(s)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
template <typename Char, size_t N,
|
||||||
|
fmt::detail_exported::fixed_string<Char, N> Str>
|
||||||
|
struct udl_compiled_string : compiled_string {
|
||||||
|
using char_type = Char;
|
||||||
|
constexpr operator basic_string_view<char_type>() const {
|
||||||
|
return {Str.data, N - 1};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename T, typename... Tail>
|
template <typename T, typename... Tail>
|
||||||
const T& first(const T& value, const Tail&...) {
|
const T& first(const T& value, const Tail&...) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part of a compiled format string. It can be either literal text or a
|
|
||||||
// replacement field.
|
|
||||||
template <typename Char> struct format_part {
|
|
||||||
enum class kind { arg_index, arg_name, text, replacement };
|
|
||||||
|
|
||||||
struct replacement {
|
|
||||||
arg_ref<Char> arg_id;
|
|
||||||
dynamic_format_specs<Char> specs;
|
|
||||||
};
|
|
||||||
|
|
||||||
kind part_kind;
|
|
||||||
union value {
|
|
||||||
int arg_index;
|
|
||||||
basic_string_view<Char> str;
|
|
||||||
replacement repl;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
|
|
||||||
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
|
|
||||||
FMT_CONSTEXPR value(replacement r) : repl(r) {}
|
|
||||||
} val;
|
|
||||||
// Position past the end of the argument id.
|
|
||||||
const Char* arg_id_end = nullptr;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
|
||||||
: part_kind(k), val(v) {}
|
|
||||||
|
|
||||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
|
||||||
return format_part(kind::arg_index, index);
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
|
||||||
return format_part(kind::arg_name, name);
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
|
||||||
return format_part(kind::text, text);
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
|
||||||
return format_part(kind::replacement, repl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char> struct part_counter {
|
|
||||||
unsigned num_parts = 0;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
|
||||||
if (begin != end) ++num_parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
|
|
||||||
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
|
|
||||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
|
|
||||||
return ++num_parts, 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
|
||||||
const Char* end) {
|
|
||||||
// Find the matching brace.
|
|
||||||
unsigned brace_counter = 0;
|
|
||||||
for (; begin != end; ++begin) {
|
|
||||||
if (*begin == '{') {
|
|
||||||
++brace_counter;
|
|
||||||
} else if (*begin == '}') {
|
|
||||||
if (brace_counter == 0u) break;
|
|
||||||
--brace_counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_error(const char*) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Counts the number of parts in a format string.
|
|
||||||
template <typename Char>
|
|
||||||
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
|
||||||
part_counter<Char> counter;
|
|
||||||
parse_format_string<true>(format_str, counter);
|
|
||||||
return counter.num_parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char, typename PartHandler>
|
|
||||||
class format_string_compiler : public error_handler {
|
|
||||||
private:
|
|
||||||
using part = format_part<Char>;
|
|
||||||
|
|
||||||
PartHandler handler_;
|
|
||||||
part part_;
|
|
||||||
basic_string_view<Char> format_str_;
|
|
||||||
basic_format_parse_context<Char> parse_context_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
|
||||||
PartHandler handler)
|
|
||||||
: handler_(handler),
|
|
||||||
format_str_(format_str),
|
|
||||||
parse_context_(format_str) {}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
|
||||||
if (begin != end)
|
|
||||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR int on_arg_id() {
|
|
||||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR int on_arg_id(int id) {
|
|
||||||
parse_context_.check_arg_id(id);
|
|
||||||
part_ = part::make_arg_index(id);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
|
|
||||||
part_ = part::make_arg_name(id);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
|
|
||||||
part_.arg_id_end = ptr;
|
|
||||||
handler_(part_);
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
|
||||||
const Char* end) {
|
|
||||||
auto repl = typename part::replacement();
|
|
||||||
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
|
|
||||||
repl.specs, parse_context_);
|
|
||||||
auto it = parse_format_specs(begin, end, handler);
|
|
||||||
if (*it != '}') on_error("missing '}' in format string");
|
|
||||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
|
||||||
? arg_ref<Char>(part_.val.arg_index)
|
|
||||||
: arg_ref<Char>(part_.val.str);
|
|
||||||
auto part = part::make_replacement(repl);
|
|
||||||
part.arg_id_end = begin;
|
|
||||||
handler_(part);
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compiles a format string and invokes handler(part) for each parsed part.
|
|
||||||
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
|
||||||
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
|
||||||
PartHandler handler) {
|
|
||||||
parse_format_string<IS_CONSTEXPR>(
|
|
||||||
format_str,
|
|
||||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Context, typename Id>
|
|
||||||
void format_arg(
|
|
||||||
basic_format_parse_context<typename Context::char_type>& parse_ctx,
|
|
||||||
Context& ctx, Id arg_id) {
|
|
||||||
ctx.advance_to(visit_format_arg(
|
|
||||||
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
|
|
||||||
ctx.arg(arg_id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
|
||||||
namespace cf {
|
|
||||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
|
||||||
auto vformat_to(OutputIt out, CompiledFormat& cf,
|
|
||||||
basic_format_args<Context> args) -> typename Context::iterator {
|
|
||||||
using char_type = typename Context::char_type;
|
|
||||||
basic_format_parse_context<char_type> parse_ctx(
|
|
||||||
to_string_view(cf.format_str_));
|
|
||||||
Context ctx(out, args);
|
|
||||||
|
|
||||||
const auto& parts = cf.parts();
|
|
||||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
|
||||||
++part_it) {
|
|
||||||
const auto& part = *part_it;
|
|
||||||
const auto& value = part.val;
|
|
||||||
|
|
||||||
using format_part_t = format_part<char_type>;
|
|
||||||
switch (part.part_kind) {
|
|
||||||
case format_part_t::kind::text: {
|
|
||||||
const auto text = value.str;
|
|
||||||
auto output = ctx.out();
|
|
||||||
auto&& it = reserve(output, text.size());
|
|
||||||
it = std::copy_n(text.begin(), text.size(), it);
|
|
||||||
ctx.advance_to(output);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case format_part_t::kind::arg_index:
|
|
||||||
advance_to(parse_ctx, part.arg_id_end);
|
|
||||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case format_part_t::kind::arg_name:
|
|
||||||
advance_to(parse_ctx, part.arg_id_end);
|
|
||||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case format_part_t::kind::replacement: {
|
|
||||||
const auto& arg_id_value = value.repl.arg_id.val;
|
|
||||||
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
|
|
||||||
? ctx.arg(arg_id_value.index)
|
|
||||||
: ctx.arg(arg_id_value.name);
|
|
||||||
|
|
||||||
auto specs = value.repl.specs;
|
|
||||||
|
|
||||||
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
|
|
||||||
handle_dynamic_spec<precision_checker>(specs.precision,
|
|
||||||
specs.precision_ref, ctx);
|
|
||||||
|
|
||||||
error_handler h;
|
|
||||||
numeric_specs_checker<error_handler> checker(h, arg.type());
|
|
||||||
if (specs.align == align::numeric) checker.require_numeric_argument();
|
|
||||||
if (specs.sign != sign::none) checker.check_sign();
|
|
||||||
if (specs.alt) checker.require_numeric_argument();
|
|
||||||
if (specs.precision >= 0) checker.check_precision();
|
|
||||||
|
|
||||||
advance_to(parse_ctx, part.arg_id_end);
|
|
||||||
ctx.advance_to(
|
|
||||||
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
|
|
||||||
ctx, nullptr, &specs),
|
|
||||||
arg));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ctx.out();
|
|
||||||
}
|
|
||||||
} // namespace cf
|
|
||||||
|
|
||||||
struct basic_compiled_format {};
|
|
||||||
|
|
||||||
template <typename S, typename = void>
|
|
||||||
struct compiled_format_base : basic_compiled_format {
|
|
||||||
using char_type = char_t<S>;
|
|
||||||
using parts_container = std::vector<detail::format_part<char_type>>;
|
|
||||||
|
|
||||||
parts_container compiled_parts;
|
|
||||||
|
|
||||||
explicit compiled_format_base(basic_string_view<char_type> format_str) {
|
|
||||||
compile_format_string<false>(format_str,
|
|
||||||
[this](const format_part<char_type>& part) {
|
|
||||||
compiled_parts.push_back(part);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts_container& parts() const { return compiled_parts; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char, unsigned N> struct format_part_array {
|
|
||||||
format_part<Char> data[N] = {};
|
|
||||||
FMT_CONSTEXPR format_part_array() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char, unsigned N>
|
|
||||||
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
|
|
||||||
basic_string_view<Char> format_str) {
|
|
||||||
format_part_array<Char, N> parts;
|
|
||||||
unsigned counter = 0;
|
|
||||||
// This is not a lambda for compatibility with older compilers.
|
|
||||||
struct {
|
|
||||||
format_part<Char>* parts;
|
|
||||||
unsigned* counter;
|
|
||||||
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
|
||||||
parts[(*counter)++] = part;
|
|
||||||
}
|
|
||||||
} collector{parts.data, &counter};
|
|
||||||
compile_format_string<true>(format_str, collector);
|
|
||||||
if (counter < N) {
|
|
||||||
parts.data[counter] =
|
|
||||||
format_part<Char>::make_text(basic_string_view<Char>());
|
|
||||||
}
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
|
|
||||||
return (a < b) ? b : a;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S>
|
|
||||||
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
|
||||||
: basic_compiled_format {
|
|
||||||
using char_type = char_t<S>;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
|
|
||||||
|
|
||||||
// Workaround for old compilers. Format string compilation will not be
|
|
||||||
// performed there anyway.
|
|
||||||
#if FMT_USE_CONSTEXPR
|
|
||||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
|
||||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
|
||||||
#else
|
|
||||||
static const unsigned num_format_parts = 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using parts_container = format_part<char_type>[num_format_parts];
|
|
||||||
|
|
||||||
const parts_container& parts() const {
|
|
||||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
|
||||||
compile_to_parts<char_type, num_format_parts>(
|
|
||||||
detail::to_string_view(S()));
|
|
||||||
return compiled_parts.data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename S, typename... Args>
|
|
||||||
class compiled_format : private compiled_format_base<S> {
|
|
||||||
public:
|
|
||||||
using typename compiled_format_base<S>::char_type;
|
|
||||||
|
|
||||||
private:
|
|
||||||
basic_string_view<char_type> format_str_;
|
|
||||||
|
|
||||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
|
||||||
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
|
|
||||||
basic_format_args<Context> args) ->
|
|
||||||
typename Context::iterator;
|
|
||||||
|
|
||||||
public:
|
|
||||||
compiled_format() = delete;
|
|
||||||
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
|
|
||||||
: compiled_format_base<S>(format_str), format_str_(format_str) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cpp_if_constexpr
|
#ifdef __cpp_if_constexpr
|
||||||
template <typename... Args> struct type_list {};
|
template <typename... Args> struct type_list {};
|
||||||
|
|
||||||
// Returns a reference to the argument at index N from [first, rest...].
|
// Returns a reference to the argument at index N from [first, rest...].
|
||||||
template <int N, typename T, typename... Args>
|
template <int N, typename T, typename... Args>
|
||||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
constexpr const auto& get([[maybe_unused]] const T& first,
|
||||||
|
[[maybe_unused]] const Args&... rest) {
|
||||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||||
if constexpr (N == 0)
|
if constexpr (N == 0)
|
||||||
return first;
|
return first;
|
||||||
|
@ -376,6 +193,12 @@ constexpr const auto& get(const T& first, const Args&... rest) {
|
||||||
return get<N - 1>(rest...);
|
return get<N - 1>(rest...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char, typename... Args>
|
||||||
|
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
||||||
|
type_list<Args...>) {
|
||||||
|
return get_arg_index_by_name<Args...>(name);
|
||||||
|
}
|
||||||
|
|
||||||
template <int N, typename> struct get_type_impl;
|
template <int N, typename> struct get_type_impl;
|
||||||
|
|
||||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||||
|
@ -392,7 +215,7 @@ template <typename Char> struct text {
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&...) const {
|
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||||
return write<Char>(out, data);
|
return write<Char>(out, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -406,32 +229,87 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||||
return {{&s[pos], size}};
|
return {{&s[pos], size}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char> struct code_unit {
|
||||||
|
Char value;
|
||||||
|
using char_type = Char;
|
||||||
|
|
||||||
|
template <typename OutputIt, typename... Args>
|
||||||
|
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||||
|
return write<Char>(out, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This ensures that the argument type is convertible to `const T&`.
|
||||||
|
template <typename T, int N, typename... Args>
|
||||||
|
constexpr const T& get_arg_checked(const Args&... args) {
|
||||||
|
const auto& arg = get<N>(args...);
|
||||||
|
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||||
|
return arg.value;
|
||||||
|
} else {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||||
|
|
||||||
// A replacement field that refers to argument N.
|
// A replacement field that refers to argument N.
|
||||||
template <typename Char, typename T, int N> struct field {
|
template <typename Char, typename T, int N> struct field {
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
// This ensures that the argument type is convertile to `const T&`.
|
return write<Char>(out, get_arg_checked<T, N>(args...));
|
||||||
const T& arg = get<N>(args...);
|
|
||||||
return write<Char>(out, arg);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename T, int N>
|
template <typename Char, typename T, int N>
|
||||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||||
|
|
||||||
|
// A replacement field that refers to argument with name.
|
||||||
|
template <typename Char> struct runtime_named_field {
|
||||||
|
using char_type = Char;
|
||||||
|
basic_string_view<Char> name;
|
||||||
|
|
||||||
|
template <typename OutputIt, typename T>
|
||||||
|
constexpr static bool try_format_argument(
|
||||||
|
OutputIt& out,
|
||||||
|
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
||||||
|
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
|
||||||
|
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
||||||
|
if (arg_name == arg.name) {
|
||||||
|
out = write<Char>(out, arg.value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename... Args>
|
||||||
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
|
bool found = (try_format_argument(out, name, args) || ...);
|
||||||
|
if (!found) {
|
||||||
|
throw format_error("argument with specified name is not found");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
||||||
|
|
||||||
// A replacement field that refers to argument N and has format specifiers.
|
// A replacement field that refers to argument N and has format specifiers.
|
||||||
template <typename Char, typename T, int N> struct spec_field {
|
template <typename Char, typename T, int N> struct spec_field {
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
mutable formatter<T, Char> fmt;
|
formatter<T, Char> fmt;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr FMT_INLINE OutputIt format(OutputIt out,
|
||||||
// This ensures that the argument type is convertile to `const T&`.
|
const Args&... args) const {
|
||||||
const T& arg = get<N>(args...);
|
const auto& vargs =
|
||||||
basic_format_context<OutputIt, Char> ctx(out, {});
|
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||||
return fmt.format(arg, ctx);
|
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||||
|
return fmt.format(get_arg_checked<T, N>(args...), ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -444,7 +322,7 @@ template <typename L, typename R> struct concat {
|
||||||
using char_type = typename L::char_type;
|
using char_type = typename L::char_type;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
out = lhs.format(out, args...);
|
out = lhs.format(out, args...);
|
||||||
return rhs.format(out, args...);
|
return rhs.format(out, args...);
|
||||||
}
|
}
|
||||||
|
@ -489,16 +367,80 @@ constexpr auto parse_tail(T head, S format_str) {
|
||||||
template <typename T, typename Char> struct parse_specs_result {
|
template <typename T, typename Char> struct parse_specs_result {
|
||||||
formatter<T, Char> fmt;
|
formatter<T, Char> fmt;
|
||||||
size_t end;
|
size_t end;
|
||||||
|
int next_arg_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr int 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) {
|
size_t pos, int next_arg_id) {
|
||||||
str.remove_prefix(pos);
|
str.remove_prefix(pos);
|
||||||
auto ctx = basic_format_parse_context<Char>(str);
|
auto ctx = basic_format_parse_context<Char>(str, {}, 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 + (end - str.data()) + 1};
|
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
||||||
|
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char> struct arg_id_handler {
|
||||||
|
arg_ref<Char> arg_id;
|
||||||
|
|
||||||
|
constexpr int operator()() {
|
||||||
|
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
constexpr int operator()(int id) {
|
||||||
|
arg_id = arg_ref<Char>(id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
constexpr int operator()(basic_string_view<Char> id) {
|
||||||
|
arg_id = arg_ref<Char>(id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void on_error(const char* message) { throw format_error(message); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char> struct parse_arg_id_result {
|
||||||
|
arg_ref<Char> arg_id;
|
||||||
|
const Char* arg_id_end;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int ID, typename Char>
|
||||||
|
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
||||||
|
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
|
||||||
|
auto arg_id_end = parse_arg_id(begin, end, handler);
|
||||||
|
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void> struct field_type {
|
||||||
|
using type = remove_cvref_t<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
|
||||||
|
using type = remove_cvref_t<decltype(T::value)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
|
||||||
|
typename S>
|
||||||
|
constexpr auto parse_replacement_field_then_tail(S format_str) {
|
||||||
|
using char_type = typename S::char_type;
|
||||||
|
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||||
|
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
|
||||||
|
if constexpr (c == '}') {
|
||||||
|
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||||
|
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
||||||
|
format_str);
|
||||||
|
} else if constexpr (c == ':') {
|
||||||
|
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||||
|
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||||
|
return parse_tail<Args, result.end, result.next_arg_id>(
|
||||||
|
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||||
|
result.fmt},
|
||||||
|
format_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compiles a non-empty format string and returns the compiled representation
|
// Compiles a non-empty format string and returns the compiled representation
|
||||||
|
@ -506,160 +448,192 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||||
template <typename Args, size_t POS, int ID, typename S>
|
template <typename Args, size_t POS, int ID, typename S>
|
||||||
constexpr auto compile_format_string(S format_str) {
|
constexpr auto compile_format_string(S format_str) {
|
||||||
using char_type = typename S::char_type;
|
using char_type = typename S::char_type;
|
||||||
constexpr basic_string_view<char_type> str = format_str;
|
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||||
if constexpr (str[POS] == '{') {
|
if constexpr (str[POS] == '{') {
|
||||||
if (POS + 1 == str.size())
|
if constexpr (POS + 1 == str.size())
|
||||||
throw format_error("unmatched '{' in format string");
|
throw format_error("unmatched '{' in format string");
|
||||||
if constexpr (str[POS + 1] == '{') {
|
if constexpr (str[POS + 1] == '{') {
|
||||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||||
} else if constexpr (str[POS + 1] == '}') {
|
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
|
||||||
using type = get_type<ID, Args>;
|
static_assert(ID != manual_indexing_id,
|
||||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
"cannot switch from manual to automatic argument indexing");
|
||||||
format_str);
|
constexpr auto next_id =
|
||||||
} else if constexpr (str[POS + 1] == ':') {
|
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||||
using type = get_type<ID, Args>;
|
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
|
||||||
constexpr auto result = parse_specs<type>(str, POS + 2);
|
POS + 1, ID, next_id>(
|
||||||
return parse_tail<Args, result.end, ID + 1>(
|
format_str);
|
||||||
spec_field<char_type, type, ID>{result.fmt}, format_str);
|
|
||||||
} else {
|
} else {
|
||||||
return unknown_format();
|
constexpr auto arg_id_result =
|
||||||
|
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
|
||||||
|
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
|
||||||
|
constexpr char_type c =
|
||||||
|
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
||||||
|
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
||||||
|
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
|
||||||
|
static_assert(
|
||||||
|
ID == manual_indexing_id || ID == 0,
|
||||||
|
"cannot switch from automatic to manual argument indexing");
|
||||||
|
constexpr auto arg_index = arg_id_result.arg_id.val.index;
|
||||||
|
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
||||||
|
Args, arg_id_end_pos,
|
||||||
|
arg_index, manual_indexing_id>(
|
||||||
|
format_str);
|
||||||
|
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||||
|
constexpr auto arg_index =
|
||||||
|
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||||
|
if constexpr (arg_index != invalid_arg_index) {
|
||||||
|
constexpr auto next_id =
|
||||||
|
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||||
|
return parse_replacement_field_then_tail<
|
||||||
|
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||||
|
arg_index, next_id>(format_str);
|
||||||
|
} else {
|
||||||
|
if constexpr (c == '}') {
|
||||||
|
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||||
|
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||||
|
format_str);
|
||||||
|
} else if constexpr (c == ':') {
|
||||||
|
return unknown_format(); // no type info for specs parsing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if constexpr (str[POS] == '}') {
|
} else if constexpr (str[POS] == '}') {
|
||||||
if (POS + 1 == str.size())
|
if constexpr (POS + 1 == str.size())
|
||||||
throw format_error("unmatched '}' in format string");
|
throw format_error("unmatched '}' in format string");
|
||||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||||
} else {
|
} else {
|
||||||
constexpr auto end = parse_text(str, POS + 1);
|
constexpr auto end = parse_text(str, POS + 1);
|
||||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
if constexpr (end - POS > 1) {
|
||||||
format_str);
|
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||||
|
format_str);
|
||||||
|
} else {
|
||||||
|
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
|
||||||
|
format_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args, typename S,
|
template <typename... Args, typename S,
|
||||||
FMT_ENABLE_IF(is_compile_string<S>::value ||
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
detail::is_compiled_string<S>::value)>
|
|
||||||
constexpr auto compile(S format_str) {
|
constexpr auto compile(S format_str) {
|
||||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
|
||||||
if constexpr (str.size() == 0) {
|
if constexpr (str.size() == 0) {
|
||||||
return detail::make_text(str, 0, 0);
|
return detail::make_text(str, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
constexpr auto result =
|
constexpr auto result =
|
||||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||||
format_str);
|
format_str);
|
||||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
return result;
|
||||||
detail::unknown_format>()) {
|
|
||||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
template <typename... Args, typename S,
|
|
||||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
|
||||||
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
|
|
||||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
|
||||||
}
|
|
||||||
#endif // __cpp_if_constexpr
|
#endif // __cpp_if_constexpr
|
||||||
|
|
||||||
// Compiles the format string which must be a string literal.
|
|
||||||
template <typename... Args, typename Char, size_t N>
|
|
||||||
auto compile(const Char (&format_str)[N])
|
|
||||||
-> detail::compiled_format<const Char*, Args...> {
|
|
||||||
return detail::compiled_format<const Char*, Args...>(
|
|
||||||
basic_string_view<Char>(format_str, N - 1));
|
|
||||||
}
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// DEPRECATED! use FMT_COMPILE instead.
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
template <typename... Args>
|
|
||||||
FMT_DEPRECATED auto compile(const Args&... args)
|
|
||||||
-> decltype(detail::compile(args...)) {
|
|
||||||
return detail::compile(args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_CONSTEXPR
|
#ifdef __cpp_if_constexpr
|
||||||
# ifdef __cpp_if_constexpr
|
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args,
|
template <typename CompiledFormat, typename... Args,
|
||||||
typename Char = typename CompiledFormat::char_type,
|
typename Char = typename CompiledFormat::char_type,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
basic_memory_buffer<Char> buffer;
|
auto s = std::basic_string<Char>();
|
||||||
detail::buffer<Char>& base = buffer;
|
cf.format(std::back_inserter(s), args...);
|
||||||
cf.format(std::back_inserter(base), args...);
|
return s;
|
||||||
return to_string(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
return cf.format(out, args...);
|
return cf.format(out, args...);
|
||||||
}
|
}
|
||||||
# endif // __cpp_if_constexpr
|
|
||||||
#endif // FMT_USE_CONSTEXPR
|
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args,
|
|
||||||
typename Char = typename CompiledFormat::char_type,
|
|
||||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
|
||||||
CompiledFormat>::value)>
|
|
||||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
|
||||||
basic_memory_buffer<Char> buffer;
|
|
||||||
using context = buffer_context<Char>;
|
|
||||||
detail::buffer<Char>& base = buffer;
|
|
||||||
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
|
|
||||||
make_format_args<context>(args...));
|
|
||||||
return to_string(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)>
|
||||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||||
Args&&... args) {
|
Args&&... args) {
|
||||||
constexpr basic_string_view<typename S::char_type> str = S();
|
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||||
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
|
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||||
return fmt::to_string(detail::first(args...));
|
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||||
|
const auto& first = detail::first(args...);
|
||||||
|
if constexpr (detail::is_named_arg<
|
||||||
|
remove_cvref_t<decltype(first)>>::value) {
|
||||||
|
return fmt::to_string(first.value);
|
||||||
|
} else {
|
||||||
|
return fmt::to_string(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
constexpr auto compiled = detail::compile<Args...>(S());
|
constexpr auto compiled = detail::compile<Args...>(S());
|
||||||
return format(compiled, std::forward<Args>(args)...);
|
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||||
}
|
detail::unknown_format>()) {
|
||||||
|
return format(static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
std::forward<Args>(args)...);
|
||||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
} else {
|
||||||
CompiledFormat>::value)>
|
return format(compiled, std::forward<Args>(args)...);
|
||||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
}
|
||||||
const Args&... args) {
|
|
||||||
using char_type = typename CompiledFormat::char_type;
|
|
||||||
using context = format_context_t<OutputIt, char_type>;
|
|
||||||
return detail::cf::vformat_to<context>(out, cf,
|
|
||||||
make_format_args<context>(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
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)>
|
||||||
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
|
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
||||||
constexpr auto compiled = detail::compile<Args...>(S());
|
constexpr auto compiled = detail::compile<Args...>(S());
|
||||||
return format_to(out, compiled, args...);
|
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||||
|
detail::unknown_format>()) {
|
||||||
|
return format_to(out,
|
||||||
|
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||||
|
std::forward<Args>(args)...);
|
||||||
|
} else {
|
||||||
|
return format_to(out, compiled, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
typename OutputIt, typename CompiledFormat, typename... Args,
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
|
|
||||||
detail::basic_compiled_format, CompiledFormat>::value)>
|
|
||||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||||
const CompiledFormat& cf,
|
const S& format_str, Args&&... args) {
|
||||||
const Args&... args) {
|
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
|
||||||
auto it =
|
std::forward<Args>(args)...);
|
||||||
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
|
|
||||||
return {it.base(), it.count()};
|
return {it.base(), it.count()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args>
|
template <typename S, typename... Args,
|
||||||
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
return format_to(detail::counting_iterator(), cf, args...).count();
|
size_t formatted_size(const S& format_str, const Args&... args) {
|
||||||
|
return format_to(detail::counting_iterator(), format_str, args...).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args,
|
||||||
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
|
void print(std::FILE* f, const S& format_str, const Args&... args) {
|
||||||
|
memory_buffer buffer;
|
||||||
|
format_to(std::back_inserter(buffer), format_str, args...);
|
||||||
|
detail::print(f, {buffer.data(), buffer.size()});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args,
|
||||||
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
|
void print(const S& format_str, const Args&... args) {
|
||||||
|
print(stdout, format_str, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
inline namespace literals {
|
||||||
|
template <detail_exported::fixed_string Str>
|
||||||
|
constexpr detail::udl_compiled_string<
|
||||||
|
remove_cvref_t<decltype(Str.data[0])>,
|
||||||
|
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
|
||||||
|
operator""_cf() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} // namespace literals
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_COMPILE_H_
|
#endif // FMT_COMPILE_H_
|
||||||
|
|
2372
include/fmt/core.h
2372
include/fmt/core.h
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
4168
include/fmt/format.h
4168
include/fmt/format.h
File diff suppressed because it is too large
Load diff
|
@ -1,78 +1,2 @@
|
||||||
// Formatting library for C++ - std::locale support
|
#include "xchar.h"
|
||||||
//
|
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// For the license information refer to format.h.
|
|
||||||
|
|
||||||
#ifndef FMT_LOCALE_H_
|
|
||||||
#define FMT_LOCALE_H_
|
|
||||||
|
|
||||||
#include <locale>
|
|
||||||
|
|
||||||
#include "format.h"
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template <typename Char>
|
|
||||||
typename buffer_context<Char>::iterator vformat_to(
|
|
||||||
const std::locale& loc, buffer<Char>& buf,
|
|
||||||
basic_string_view<Char> format_str,
|
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
|
||||||
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
|
|
||||||
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
|
|
||||||
args, detail::locale_ref(loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
std::basic_string<Char> vformat(
|
|
||||||
const std::locale& loc, basic_string_view<Char> format_str,
|
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
|
||||||
basic_memory_buffer<Char> buffer;
|
|
||||||
detail::vformat_to(loc, buffer, format_str, args);
|
|
||||||
return fmt::to_string(buffer);
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
|
||||||
inline std::basic_string<Char> vformat(
|
|
||||||
const std::locale& loc, const S& format_str,
|
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
|
||||||
return detail::vformat(loc, to_string_view(format_str), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
|
||||||
inline std::basic_string<Char> format(const std::locale& loc,
|
|
||||||
const S& format_str, Args&&... args) {
|
|
||||||
return detail::vformat(
|
|
||||||
loc, to_string_view(format_str),
|
|
||||||
detail::make_args_checked<Args...>(format_str, args...));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename OutputIt, typename... Args,
|
|
||||||
typename Char = enable_if_t<
|
|
||||||
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
|
|
||||||
inline OutputIt vformat_to(
|
|
||||||
OutputIt out, const std::locale& loc, const S& format_str,
|
|
||||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
|
||||||
using af = detail::arg_formatter<OutputIt, Char>;
|
|
||||||
return vformat_to<af>(out, to_string_view(format_str), args,
|
|
||||||
detail::locale_ref(loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
|
||||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
|
|
||||||
detail::is_string<S>::value)>
|
|
||||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
|
||||||
const S& format_str, Args&&... args) {
|
|
||||||
detail::check_format_string<Args...>(format_str);
|
|
||||||
using context = format_context_t<OutputIt, char_t<S>>;
|
|
||||||
format_arg_store<context, Args...> as{args...};
|
|
||||||
return vformat_to(out, loc, to_string_view(format_str),
|
|
||||||
basic_format_args<context>(as));
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
|
||||||
|
|
||||||
#endif // FMT_LOCALE_H_
|
|
||||||
|
|
259
include/fmt/os.h
259
include/fmt/os.h
|
@ -8,16 +8,12 @@
|
||||||
#ifndef FMT_OS_H_
|
#ifndef FMT_OS_H_
|
||||||
#define FMT_OS_H_
|
#define FMT_OS_H_
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
|
||||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
|
||||||
# undef __STRICT_ANSI__
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <clocale> // for locale_t
|
#include <clocale> // locale_t
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib> // for strtod_l
|
#include <cstdlib> // strtod_l
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
|
||||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||||
|
@ -29,7 +25,8 @@
|
||||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||||
# include <winapifamily.h>
|
# include <winapifamily.h>
|
||||||
#endif
|
#endif
|
||||||
#if FMT_HAS_INCLUDE("fcntl.h") && \
|
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||||
|
defined(__linux__)) && \
|
||||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||||
# include <fcntl.h> // for O_RDONLY
|
# include <fcntl.h> // for O_RDONLY
|
||||||
# define FMT_USE_FCNTL 1
|
# define FMT_USE_FCNTL 1
|
||||||
|
@ -73,6 +70,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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\rst
|
\rst
|
||||||
|
@ -121,19 +119,28 @@ 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>;
|
||||||
|
|
||||||
// An error code.
|
template <typename Char> struct formatter<std::error_code, Char> {
|
||||||
class error_code {
|
template <typename ParseContext>
|
||||||
private:
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
int value_;
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
template <typename FormatContext>
|
||||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
int get() const FMT_NOEXCEPT { return value_; }
|
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
|
||||||
namespace detail {
|
FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
// A converter from UTF-16 to UTF-8.
|
// A converter from UTF-16 to UTF-8.
|
||||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||||
class utf16_to_utf8 {
|
class utf16_to_utf8 {
|
||||||
|
@ -142,7 +149,7 @@ class utf16_to_utf8 {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
utf16_to_utf8() {}
|
utf16_to_utf8() {}
|
||||||
FMT_API explicit utf16_to_utf8(wstring_view s);
|
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
||||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||||
size_t size() const { return buffer_.size() - 1; }
|
size_t size() const { return buffer_.size() - 1; }
|
||||||
const char* c_str() const { return &buffer_[0]; }
|
const char* c_str() const { return &buffer_[0]; }
|
||||||
|
@ -151,59 +158,68 @@ class utf16_to_utf8 {
|
||||||
// Performs conversion returning a system error code instead of
|
// Performs conversion returning a system error code instead of
|
||||||
// throwing exception on conversion error. This method may still throw
|
// throwing exception on conversion error. This method may still throw
|
||||||
// in case of memory allocation error.
|
// in case of memory allocation error.
|
||||||
FMT_API int convert(wstring_view s);
|
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,
|
||||||
string_view message) FMT_NOEXCEPT;
|
const char* message) FMT_NOEXCEPT;
|
||||||
} // namespace detail
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
/** A Windows error. */
|
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||||
class windows_error : public system_error {
|
format_args args);
|
||||||
private:
|
|
||||||
FMT_API void init(int error_code, string_view format_str, format_args args);
|
|
||||||
|
|
||||||
public:
|
/**
|
||||||
/**
|
\rst
|
||||||
\rst
|
Constructs a :class:`std::system_error` object with the description
|
||||||
Constructs a :class:`fmt::windows_error` object with the description
|
of the form
|
||||||
of the form
|
|
||||||
|
|
||||||
.. parsed-literal::
|
.. parsed-literal::
|
||||||
*<message>*: *<system-message>*
|
*<message>*: *<system-message>*
|
||||||
|
|
||||||
where *<message>* is the formatted message and *<system-message>* is the
|
where *<message>* is the formatted message and *<system-message>* is the
|
||||||
system message corresponding to the error code.
|
system message corresponding to the error code.
|
||||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||||
If *error_code* is not a valid error code such as -1, the system message
|
If *error_code* is not a valid error code such as -1, the system message
|
||||||
will look like "error -1".
|
will look like "error -1".
|
||||||
|
|
||||||
**Example**::
|
**Example**::
|
||||||
|
|
||||||
// This throws a windows_error with the description
|
// This throws a system_error with the description
|
||||||
// cannot open file 'madeup': The system cannot find the file specified.
|
// cannot open file 'madeup': The system cannot find the file specified.
|
||||||
// or similar (system message may vary).
|
// or similar (system message may vary).
|
||||||
const char *filename = "madeup";
|
const char *filename = "madeup";
|
||||||
LPOFSTRUCT of = LPOFSTRUCT();
|
LPOFSTRUCT of = LPOFSTRUCT();
|
||||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||||
if (file == HFILE_ERROR) {
|
if (file == HFILE_ERROR) {
|
||||||
throw fmt::windows_error(GetLastError(),
|
throw fmt::windows_error(GetLastError(),
|
||||||
"cannot open file '{}'", filename);
|
"cannot open file '{}'", filename);
|
||||||
}
|
}
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
windows_error(int error_code, string_view message, const Args&... args) {
|
std::system_error windows_error(int error_code, string_view message,
|
||||||
init(error_code, message, make_format_args(args...));
|
const Args&... args) {
|
||||||
}
|
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
||||||
};
|
}
|
||||||
|
|
||||||
// Reports a Windows error without throwing an exception.
|
// Reports a Windows error without throwing an exception.
|
||||||
// Can be used to report errors from destructors.
|
// Can be used to report errors from destructors.
|
||||||
FMT_API void report_windows_error(int error_code,
|
FMT_API void report_windows_error(int error_code,
|
||||||
string_view message) FMT_NOEXCEPT;
|
const char* message) FMT_NOEXCEPT;
|
||||||
|
#else
|
||||||
|
inline const std::error_category& system_category() FMT_NOEXCEPT {
|
||||||
|
return std::system_category();
|
||||||
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
|
// std::system is not available on some platforms such as iOS (#2248).
|
||||||
|
#ifdef __OSX__
|
||||||
|
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||||
|
void say(const S& format_str, Args&&... args) {
|
||||||
|
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// A buffered file.
|
// A buffered file.
|
||||||
class buffered_file {
|
class buffered_file {
|
||||||
private:
|
private:
|
||||||
|
@ -254,7 +270,7 @@ class buffered_file {
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
inline void print(string_view format_str, const Args&... args) {
|
inline void print(string_view format_str, const Args&... args) {
|
||||||
vprint(format_str, make_format_args(args...));
|
vprint(format_str, fmt::make_format_args(args...));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -278,7 +294,9 @@ class file {
|
||||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||||
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
|
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
|
||||||
|
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
|
||||||
|
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constructs a file object which doesn't represent any file.
|
// Constructs a file object which doesn't represent any file.
|
||||||
|
@ -293,7 +311,8 @@ class file {
|
||||||
|
|
||||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||||
|
|
||||||
file& operator=(file&& other) FMT_NOEXCEPT {
|
// Move assignment is not noexcept because close may throw.
|
||||||
|
file& operator=(file&& other) {
|
||||||
close();
|
close();
|
||||||
fd_ = other.fd_;
|
fd_ = other.fd_;
|
||||||
other.fd_ = -1;
|
other.fd_ = -1;
|
||||||
|
@ -329,7 +348,7 @@ class file {
|
||||||
|
|
||||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||||
// necessary.
|
// necessary.
|
||||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
|
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
|
||||||
|
|
||||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||||
// and writing respectively.
|
// and writing respectively.
|
||||||
|
@ -343,62 +362,107 @@ class file {
|
||||||
// Returns the memory page size.
|
// Returns the memory page size.
|
||||||
long getpagesize();
|
long getpagesize();
|
||||||
|
|
||||||
class direct_buffered_file;
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
template <typename S, typename... Args>
|
struct buffer_size {
|
||||||
void print(direct_buffered_file& f, const S& format_str,
|
buffer_size() = default;
|
||||||
const Args&... args);
|
size_t value = 0;
|
||||||
|
buffer_size operator=(size_t val) const {
|
||||||
|
auto bs = buffer_size();
|
||||||
|
bs.value = val;
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// A buffered file with a direct buffer access and no synchronization.
|
struct ostream_params {
|
||||||
class direct_buffered_file {
|
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||||
|
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||||
|
|
||||||
|
ostream_params() {}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||||
|
oflag = new_oflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
ostream_params(T... params, detail::buffer_size bs)
|
||||||
|
: ostream_params(params...) {
|
||||||
|
this->buffer_size = bs.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
static constexpr detail::buffer_size buffer_size;
|
||||||
|
|
||||||
|
/** A fast output stream which is not thread-safe. */
|
||||||
|
class FMT_API ostream final : private detail::buffer<char> {
|
||||||
private:
|
private:
|
||||||
file file_;
|
file file_;
|
||||||
|
|
||||||
enum { buffer_size = 4096 };
|
|
||||||
char buffer_[buffer_size];
|
|
||||||
int pos_;
|
|
||||||
|
|
||||||
void flush() {
|
void flush() {
|
||||||
if (pos_ == 0) return;
|
if (size() == 0) return;
|
||||||
file_.write(buffer_, pos_);
|
file_.write(data(), size());
|
||||||
pos_ = 0;
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int free_capacity() const { return buffer_size - pos_; }
|
void grow(size_t) override;
|
||||||
|
|
||||||
|
ostream(cstring_view path, const detail::ostream_params& params)
|
||||||
|
: file_(path, params.oflag) {
|
||||||
|
set(new char[params.buffer_size], params.buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
direct_buffered_file(cstring_view path, int oflag)
|
ostream(ostream&& other)
|
||||||
: file_(path, oflag), pos_(0) {}
|
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||||
|
file_(std::move(other.file_)) {
|
||||||
~direct_buffered_file() {
|
other.clear();
|
||||||
flush();
|
other.set(nullptr, 0);
|
||||||
}
|
}
|
||||||
|
~ostream() {
|
||||||
|
flush();
|
||||||
|
delete[] data();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
friend ostream output_file(cstring_view path, T... params);
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
flush();
|
flush();
|
||||||
file_.close();
|
file_.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename... Args>
|
/**
|
||||||
friend void print(direct_buffered_file& f, const S& format_str,
|
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||||
const Args&... args) {
|
output to the file.
|
||||||
// We could avoid double buffering.
|
*/
|
||||||
auto buf = fmt::memory_buffer();
|
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||||
fmt::format_to(std::back_inserter(buf), format_str, args...);
|
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
||||||
auto remaining_pos = 0;
|
fmt::make_format_args(args...));
|
||||||
auto remaining_size = buf.size();
|
|
||||||
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
|
|
||||||
auto size = f.free_capacity();
|
|
||||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
|
|
||||||
f.pos_ += size;
|
|
||||||
f.flush();
|
|
||||||
remaining_pos += size;
|
|
||||||
remaining_size -= size;
|
|
||||||
}
|
|
||||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
|
|
||||||
f.pos_ += static_cast<int>(remaining_size);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Opens a file for writing. Supported parameters passed in *params*:
|
||||||
|
|
||||||
|
* ``<integer>``: Flags passed to `open
|
||||||
|
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
|
||||||
|
(``file::WRONLY | file::CREATE`` by default)
|
||||||
|
* ``buffer_size=<integer>``: Output buffer size
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
auto out = fmt::output_file("guide.txt");
|
||||||
|
out.print("Don't {}", "Panic");
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename... T>
|
||||||
|
inline ostream output_file(cstring_view path, T... params) {
|
||||||
|
return {path, detail::ostream_params(params...)};
|
||||||
|
}
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
#ifdef FMT_LOCALE
|
#ifdef FMT_LOCALE
|
||||||
|
@ -445,6 +509,7 @@ class locale {
|
||||||
};
|
};
|
||||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||||
#endif // FMT_LOCALE
|
#endif // FMT_LOCALE
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_OS_H_
|
#endif // FMT_OS_H_
|
||||||
|
|
|
@ -49,17 +49,27 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct converter {
|
||||||
|
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||||
private:
|
private:
|
||||||
// Hide all operator<< from std::basic_ostream<Char>.
|
void_t<> operator<<(converter);
|
||||||
void_t<> operator<<(null<>);
|
|
||||||
void_t<> operator<<(const Char*);
|
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
|
||||||
!std::is_enum<T>::value)>
|
|
||||||
void_t<> operator<<(T);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Hide insertion operators for built-in types.
|
||||||
|
template <typename Char, typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
|
||||||
|
template <typename Char, typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
|
||||||
|
template <typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
|
||||||
|
template <typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
|
||||||
|
template <typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
|
||||||
|
|
||||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||||
// std::ostream).
|
// std::ostream).
|
||||||
template <typename T, typename Char> class is_streamable {
|
template <typename T, typename Char> class is_streamable {
|
||||||
|
@ -75,6 +85,8 @@ template <typename T, typename Char> class is_streamable {
|
||||||
using result = decltype(test<T>(0));
|
using result = decltype(test<T>(0));
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
is_streamable() = default;
|
||||||
|
|
||||||
static const bool value = result::value;
|
static const bool value = result::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,7 +115,7 @@ void format_value(buffer<Char>& buf, const T& value,
|
||||||
#endif
|
#endif
|
||||||
output << value;
|
output << value;
|
||||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||||
buf.resize(buf.size());
|
buf.try_resize(buf.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||||
|
@ -139,6 +151,7 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||||
};
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
void vprint(std::basic_ostream<Char>& os, 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) {
|
||||||
|
@ -156,11 +169,12 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||||
fmt::print(cerr, "Don't {}!", "panic");
|
fmt::print(cerr, "Don't {}!", "panic");
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
|
FMT_MODULE_EXPORT
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
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>>>
|
||||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||||
vprint(os, to_string_view(format_str),
|
vprint(os, to_string_view(format_str),
|
||||||
detail::make_args_checked<Args...>(format_str, args...));
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
}
|
}
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#include "os.h"
|
|
||||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
|
|
@ -10,11 +10,54 @@
|
||||||
|
|
||||||
#include <algorithm> // std::max
|
#include <algorithm> // std::max
|
||||||
#include <limits> // std::numeric_limits
|
#include <limits> // std::numeric_limits
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
#include "ostream.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace detail {
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||||
|
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||||
|
private:
|
||||||
|
OutputIt out_;
|
||||||
|
basic_format_args<basic_printf_context> args_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using char_type = Char;
|
||||||
|
using format_arg = basic_format_arg<basic_printf_context>;
|
||||||
|
using parse_context_type = basic_printf_parse_context<Char>;
|
||||||
|
template <typename T> using formatter_type = printf_formatter<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Constructs a ``printf_context`` object. References to the arguments are
|
||||||
|
stored in the context object so make sure they have appropriate lifetimes.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
basic_printf_context(OutputIt out,
|
||||||
|
basic_format_args<basic_printf_context> args)
|
||||||
|
: out_(out), args_(args) {}
|
||||||
|
|
||||||
|
OutputIt out() { return out_; }
|
||||||
|
void advance_to(OutputIt it) { out_ = it; }
|
||||||
|
|
||||||
|
detail::locale_ref locale() { return {}; }
|
||||||
|
|
||||||
|
format_arg arg(int id) const { return args_.get(id); }
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void on_error(const char* message) {
|
||||||
|
detail::error_handler().on_error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -178,79 +221,34 @@ template <typename Char> class printf_width_handler {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Context>
|
// The ``printf`` argument formatter.
|
||||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
|
||||||
basic_format_args<Context> args) {
|
|
||||||
Context(std::back_inserter(buf), format, args).format();
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
// For printing into memory_buffer.
|
|
||||||
template <typename Char, typename Context>
|
|
||||||
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
|
|
||||||
basic_string_view<Char> format,
|
|
||||||
basic_format_args<Context> args) {
|
|
||||||
return detail::vprintf(buf, format, args);
|
|
||||||
}
|
|
||||||
using detail::vprintf;
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
|
||||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
|
||||||
};
|
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
\rst
|
|
||||||
The ``printf`` argument formatter.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
template <typename OutputIt, typename Char>
|
template <typename OutputIt, typename Char>
|
||||||
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
class printf_arg_formatter : public arg_formatter<Char> {
|
||||||
public:
|
|
||||||
using iterator = OutputIt;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using char_type = Char;
|
using base = arg_formatter<Char>;
|
||||||
using base = detail::arg_formatter_base<OutputIt, Char>;
|
|
||||||
using context_type = basic_printf_context<OutputIt, Char>;
|
using context_type = basic_printf_context<OutputIt, Char>;
|
||||||
|
using format_specs = basic_format_specs<Char>;
|
||||||
|
|
||||||
context_type& context_;
|
context_type& context_;
|
||||||
|
|
||||||
void write_null_pointer(char) {
|
OutputIt write_null_pointer(bool is_string = false) {
|
||||||
this->specs()->type = 0;
|
auto s = this->specs;
|
||||||
this->write("(nil)");
|
s.type = 0;
|
||||||
}
|
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||||
|
|
||||||
void write_null_pointer(wchar_t) {
|
|
||||||
this->specs()->type = 0;
|
|
||||||
this->write(L"(nil)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using format_specs = typename base::format_specs;
|
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
||||||
|
: base{iter, s, locale_ref()}, context_(ctx) {}
|
||||||
|
|
||||||
/**
|
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||||
\rst
|
|
||||||
Constructs an argument formatter object.
|
|
||||||
*buffer* is a reference to the output buffer and *specs* contains format
|
|
||||||
specifier information for standard argument types.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
|
||||||
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
|
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||||
iterator operator()(T value) {
|
OutputIt operator()(T value) {
|
||||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||||
// use std::is_same instead.
|
// std::is_same instead.
|
||||||
if (std::is_same<T, bool>::value) {
|
if (std::is_same<T, Char>::value) {
|
||||||
format_specs& fmt_specs = *this->specs();
|
format_specs fmt_specs = this->specs;
|
||||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
|
|
||||||
fmt_specs.type = 0;
|
|
||||||
this->write(value != 0);
|
|
||||||
} else if (std::is_same<T, char_type>::value) {
|
|
||||||
format_specs& fmt_specs = *this->specs();
|
|
||||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||||
return (*this)(static_cast<int>(value));
|
return (*this)(static_cast<int>(value));
|
||||||
fmt_specs.sign = sign::none;
|
fmt_specs.sign = sign::none;
|
||||||
|
@ -260,138 +258,49 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
||||||
// ignored for non-numeric types
|
// ignored for non-numeric types
|
||||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||||
fmt_specs.align = align::right;
|
fmt_specs.align = align::right;
|
||||||
return base::operator()(value);
|
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||||
} else {
|
|
||||||
return base::operator()(value);
|
|
||||||
}
|
}
|
||||||
return this->out();
|
return base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||||
iterator operator()(T value) {
|
OutputIt operator()(T value) {
|
||||||
return base::operator()(value);
|
return base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats a null-terminated C string. */
|
/** Formats a null-terminated C string. */
|
||||||
iterator operator()(const char* value) {
|
OutputIt operator()(const char* value) {
|
||||||
if (value)
|
if (value) return base::operator()(value);
|
||||||
base::operator()(value);
|
return write_null_pointer(this->specs.type != 'p');
|
||||||
else if (this->specs()->type == 'p')
|
|
||||||
write_null_pointer(char_type());
|
|
||||||
else
|
|
||||||
this->write("(null)");
|
|
||||||
return this->out();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats a null-terminated wide C string. */
|
/** Formats a null-terminated wide C string. */
|
||||||
iterator operator()(const wchar_t* value) {
|
OutputIt operator()(const wchar_t* value) {
|
||||||
if (value)
|
if (value) return base::operator()(value);
|
||||||
base::operator()(value);
|
return write_null_pointer(this->specs.type != 'p');
|
||||||
else if (this->specs()->type == 'p')
|
|
||||||
write_null_pointer(char_type());
|
|
||||||
else
|
|
||||||
this->write(L"(null)");
|
|
||||||
return this->out();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator operator()(basic_string_view<char_type> value) {
|
OutputIt operator()(basic_string_view<Char> value) {
|
||||||
return base::operator()(value);
|
return base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator operator()(monostate value) { return base::operator()(value); }
|
|
||||||
|
|
||||||
/** Formats a pointer. */
|
/** Formats a pointer. */
|
||||||
iterator operator()(const void* value) {
|
OutputIt operator()(const void* value) {
|
||||||
if (value) return base::operator()(value);
|
return value ? base::operator()(value) : write_null_pointer();
|
||||||
this->specs()->type = 0;
|
|
||||||
write_null_pointer(char_type());
|
|
||||||
return this->out();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats an argument of a custom (user-defined) type. */
|
/** Formats an argument of a custom (user-defined) type. */
|
||||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||||
handle.format(context_.parse_context(), context_);
|
auto parse_ctx =
|
||||||
return this->out();
|
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
||||||
|
handle.format(parse_ctx, context_);
|
||||||
|
return this->out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T> struct printf_formatter {
|
template <typename Char>
|
||||||
printf_formatter() = delete;
|
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||||
|
const Char* end) {
|
||||||
template <typename ParseContext>
|
|
||||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
|
|
||||||
detail::format_value(detail::get_container(ctx.out()), value);
|
|
||||||
return ctx.out();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
This template formats data and writes the output through an output iterator.
|
|
||||||
*/
|
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
|
||||||
public:
|
|
||||||
/** The character type for the output. */
|
|
||||||
using char_type = Char;
|
|
||||||
using iterator = OutputIt;
|
|
||||||
using format_arg = basic_format_arg<basic_printf_context>;
|
|
||||||
using parse_context_type = basic_printf_parse_context<Char>;
|
|
||||||
template <typename T> using formatter_type = printf_formatter<T>;
|
|
||||||
|
|
||||||
private:
|
|
||||||
using format_specs = basic_format_specs<char_type>;
|
|
||||||
|
|
||||||
OutputIt out_;
|
|
||||||
basic_format_args<basic_printf_context> args_;
|
|
||||||
parse_context_type parse_ctx_;
|
|
||||||
|
|
||||||
static void parse_flags(format_specs& specs, const Char*& it,
|
|
||||||
const Char* end);
|
|
||||||
|
|
||||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
|
||||||
// argument.
|
|
||||||
format_arg get_arg(int arg_index = -1);
|
|
||||||
|
|
||||||
// Parses argument index, flags and width and returns the argument index.
|
|
||||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
\rst
|
|
||||||
Constructs a ``printf_context`` object. References to the arguments are
|
|
||||||
stored in the context object so make sure they have appropriate lifetimes.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
|
||||||
basic_format_args<basic_printf_context> args)
|
|
||||||
: out_(out), args_(args), parse_ctx_(format_str) {}
|
|
||||||
|
|
||||||
OutputIt out() { return out_; }
|
|
||||||
void advance_to(OutputIt it) { out_ = it; }
|
|
||||||
|
|
||||||
detail::locale_ref locale() { return {}; }
|
|
||||||
|
|
||||||
format_arg arg(int id) const { return args_.get(id); }
|
|
||||||
|
|
||||||
parse_context_type& parse_context() { return parse_ctx_; }
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_error(const char* message) {
|
|
||||||
parse_ctx_.on_error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Formats stored arguments and writes the output to the range. */
|
|
||||||
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
|
|
||||||
OutputIt format();
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
|
||||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
|
||||||
const Char*& it,
|
|
||||||
const Char* end) {
|
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
switch (*it) {
|
switch (*it) {
|
||||||
case '-':
|
case '-':
|
||||||
|
@ -417,35 +326,24 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
template <typename Char, typename GetArg>
|
||||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
int parse_header(const Char*& it, const Char* end,
|
||||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
basic_format_specs<Char>& specs, GetArg get_arg) {
|
||||||
if (arg_index < 0)
|
|
||||||
arg_index = parse_ctx_.next_arg_id();
|
|
||||||
else
|
|
||||||
parse_ctx_.check_arg_id(--arg_index);
|
|
||||||
return detail::get_arg(*this, arg_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
|
||||||
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
|
||||||
const Char* end,
|
|
||||||
format_specs& specs) {
|
|
||||||
int arg_index = -1;
|
int arg_index = -1;
|
||||||
char_type c = *it;
|
Char c = *it;
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
// Parse an argument index (if followed by '$') or a width possibly
|
// Parse an argument index (if followed by '$') or a width possibly
|
||||||
// preceded with '0' flag(s).
|
// preceded with '0' flag(s).
|
||||||
detail::error_handler eh;
|
int value = parse_nonnegative_int(it, end, -1);
|
||||||
int value = parse_nonnegative_int(it, end, eh);
|
|
||||||
if (it != end && *it == '$') { // value is an argument index
|
if (it != end && *it == '$') { // value is an argument index
|
||||||
++it;
|
++it;
|
||||||
arg_index = value;
|
arg_index = value != -1 ? value : max_value<int>();
|
||||||
} else {
|
} else {
|
||||||
if (c == '0') specs.fill[0] = '0';
|
if (c == '0') specs.fill[0] = '0';
|
||||||
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"));
|
||||||
specs.width = value;
|
specs.width = value;
|
||||||
return arg_index;
|
return arg_index;
|
||||||
}
|
}
|
||||||
|
@ -455,58 +353,76 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||||
// Parse width.
|
// Parse width.
|
||||||
if (it != end) {
|
if (it != end) {
|
||||||
if (*it >= '0' && *it <= '9') {
|
if (*it >= '0' && *it <= '9') {
|
||||||
detail::error_handler eh;
|
specs.width = parse_nonnegative_int(it, end, -1);
|
||||||
specs.width = parse_nonnegative_int(it, end, eh);
|
if (specs.width == -1) FMT_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(
|
||||||
detail::printf_width_handler<char_type>(specs), get_arg()));
|
detail::printf_width_handler<Char>(specs), get_arg(-1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arg_index;
|
return arg_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
template <typename Char, typename Context>
|
||||||
template <typename ArgFormatter>
|
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
OutputIt basic_printf_context<OutputIt, Char>::format() {
|
basic_format_args<Context> args) {
|
||||||
auto out = this->out();
|
using OutputIt = buffer_appender<Char>;
|
||||||
const Char* start = parse_ctx_.begin();
|
auto out = OutputIt(buf);
|
||||||
const Char* end = parse_ctx_.end();
|
auto context = basic_printf_context<OutputIt, Char>(out, args);
|
||||||
|
auto parse_ctx = basic_printf_parse_context<Char>(format);
|
||||||
|
|
||||||
|
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||||
|
// argument.
|
||||||
|
auto get_arg = [&](int arg_index) {
|
||||||
|
if (arg_index < 0)
|
||||||
|
arg_index = parse_ctx.next_arg_id();
|
||||||
|
else
|
||||||
|
parse_ctx.check_arg_id(--arg_index);
|
||||||
|
return detail::get_arg(context, arg_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Char* start = parse_ctx.begin();
|
||||||
|
const Char* end = parse_ctx.end();
|
||||||
auto it = start;
|
auto it = start;
|
||||||
while (it != end) {
|
while (it != end) {
|
||||||
char_type c = *it++;
|
if (!detail::find<false, Char>(it, end, '%', it)) {
|
||||||
if (c != '%') continue;
|
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Char c = *it++;
|
||||||
if (it != end && *it == c) {
|
if (it != end && *it == c) {
|
||||||
out = std::copy(start, it, out);
|
out = detail::write(
|
||||||
|
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||||
start = ++it;
|
start = ++it;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
out = std::copy(start, it - 1, out);
|
out = detail::write(out, basic_string_view<Char>(
|
||||||
|
start, detail::to_unsigned(it - 1 - start)));
|
||||||
|
|
||||||
format_specs specs;
|
basic_format_specs<Char> specs;
|
||||||
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);
|
int arg_index = parse_header(it, end, specs, get_arg);
|
||||||
if (arg_index == 0) on_error("argument not found");
|
if (arg_index == 0) parse_ctx.on_error("argument not found");
|
||||||
|
|
||||||
// Parse precision.
|
// Parse precision.
|
||||||
if (it != end && *it == '.') {
|
if (it != end && *it == '.') {
|
||||||
++it;
|
++it;
|
||||||
c = it != end ? *it : 0;
|
c = it != end ? *it : 0;
|
||||||
if ('0' <= c && c <= '9') {
|
if ('0' <= c && c <= '9') {
|
||||||
detail::error_handler eh;
|
specs.precision = parse_nonnegative_int(it, end, 0);
|
||||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
|
||||||
} 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()));
|
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
|
||||||
} else {
|
} else {
|
||||||
specs.precision = 0;
|
specs.precision = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
format_arg 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())
|
||||||
|
@ -516,9 +432,10 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
auto str = visit_format_arg(detail::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>(basic_string_view<Char>(
|
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
|
||||||
str,
|
basic_string_view<Char>(
|
||||||
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
|
str, detail::to_unsigned(nul != str_end ? nul - str
|
||||||
|
: specs.precision)));
|
||||||
}
|
}
|
||||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||||
specs.alt = false;
|
specs.alt = false;
|
||||||
|
@ -532,7 +449,7 @@ OutputIt basic_printf_context<OutputIt, 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_type t = it != end ? *it : 0;
|
Char t = it != end ? *it : 0;
|
||||||
using detail::convert_arg;
|
using detail::convert_arg;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'h':
|
case 'h':
|
||||||
|
@ -582,8 +499,9 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||||
specs.type = 'd';
|
specs.type = 'd';
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
|
visit_format_arg(
|
||||||
arg);
|
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
|
||||||
|
arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,14 +509,16 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||||
start = it;
|
start = it;
|
||||||
|
|
||||||
// Format argument.
|
// Format argument.
|
||||||
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
out = visit_format_arg(
|
||||||
|
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
|
||||||
}
|
}
|
||||||
return std::copy(start, it, out);
|
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||||
}
|
}
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
using basic_printf_context_t =
|
using basic_printf_context_t =
|
||||||
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||||
|
|
||||||
using printf_context = basic_printf_context_t<char>;
|
using printf_context = basic_printf_context_t<char>;
|
||||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||||
|
@ -612,9 +532,9 @@ using wprintf_args = basic_format_args<wprintf_context>;
|
||||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... T>
|
||||||
inline format_arg_store<printf_context, Args...> make_printf_args(
|
inline auto make_printf_args(const T&... args)
|
||||||
const Args&... args) {
|
-> format_arg_store<printf_context, T...> {
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,18 +544,19 @@ inline format_arg_store<printf_context, Args...> make_printf_args(
|
||||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... T>
|
||||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
|
inline auto make_wprintf_args(const T&... args)
|
||||||
const Args&... args) {
|
-> format_arg_store<wprintf_context, T...> {
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline std::basic_string<Char> vsprintf(
|
inline auto vsprintf(
|
||||||
const S& format,
|
const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
vprintf(buffer, to_string_view(format), args);
|
vprintf(buffer, to_string_view(fmt), args);
|
||||||
return to_string(buffer);
|
return to_string(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,19 +569,20 @@ inline std::basic_string<Char> vsprintf(
|
||||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
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 std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||||
using context = basic_printf_context_t<Char>;
|
using context = basic_printf_context_t<Char>;
|
||||||
return vsprintf(to_string_view(format), make_format_args<context>(args...));
|
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline int vfprintf(
|
inline auto vfprintf(
|
||||||
std::FILE* f, const S& format,
|
std::FILE* f, const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
|
-> int {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
vprintf(buffer, to_string_view(format), args);
|
vprintf(buffer, to_string_view(fmt), args);
|
||||||
size_t size = buffer.size();
|
size_t size = buffer.size();
|
||||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||||
? -1
|
? -1
|
||||||
|
@ -676,19 +598,19 @@ inline int vfprintf(
|
||||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T, typename Char = char_t<S>>
|
||||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
|
||||||
using context = basic_printf_context_t<Char>;
|
using context = basic_printf_context_t<Char>;
|
||||||
return vfprintf(f, to_string_view(format),
|
return vfprintf(f, to_string_view(fmt),
|
||||||
make_format_args<context>(args...));
|
fmt::make_format_args<context>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline int vprintf(
|
inline auto vprintf(
|
||||||
const S& format,
|
const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
return vfprintf(stdout, to_string_view(format), args);
|
-> int {
|
||||||
|
return vfprintf(stdout, to_string_view(fmt), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -700,52 +622,31 @@ inline int vprintf(
|
||||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||||
inline int printf(const S& format_str, const Args&... args) {
|
return vprintf(
|
||||||
using context = basic_printf_context_t<char_t<S>>;
|
to_string_view(fmt),
|
||||||
return vprintf(to_string_view(format_str),
|
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||||
make_format_args<context>(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline int vfprintf(
|
FMT_DEPRECATED auto vfprintf(
|
||||||
std::basic_ostream<Char>& os, const S& format,
|
std::basic_ostream<Char>& os, const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
|
-> int {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
vprintf(buffer, to_string_view(format), args);
|
vprintf(buffer, to_string_view(fmt), args);
|
||||||
detail::write_buffer(os, buffer);
|
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||||
return static_cast<int>(buffer.size());
|
return static_cast<int>(buffer.size());
|
||||||
}
|
}
|
||||||
|
template <typename S, typename... T, typename Char = char_t<S>>
|
||||||
/** Formats arguments and writes the output to the range. */
|
FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
|
||||||
template <typename ArgFormatter, typename Char,
|
const T&... args) -> int {
|
||||||
typename Context =
|
return vfprintf(os, to_string_view(fmt),
|
||||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
|
||||||
typename ArgFormatter::iterator vprintf(
|
|
||||||
detail::buffer<Char>& out, basic_string_view<Char> format_str,
|
|
||||||
basic_format_args<type_identity_t<Context>> args) {
|
|
||||||
typename ArgFormatter::iterator iter(out);
|
|
||||||
Context(iter, format_str, args).template format<ArgFormatter>();
|
|
||||||
return iter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
FMT_MODULE_EXPORT_END
|
||||||
\rst
|
|
||||||
Prints formatted data to the stream *os*.
|
|
||||||
|
|
||||||
**Example**::
|
|
||||||
|
|
||||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
|
||||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
|
|
||||||
const Args&... args) {
|
|
||||||
using context = basic_printf_context_t<Char>;
|
|
||||||
return vfprintf(os, to_string_view(format_str),
|
|
||||||
make_format_args<context>(args...));
|
|
||||||
}
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_PRINTF_H_
|
#endif // FMT_PRINTF_H_
|
||||||
|
|
|
@ -17,41 +17,31 @@
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
// output only up to N items from the range.
|
|
||||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
|
||||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
template <typename Char> struct formatting_base {
|
template <typename Char, typename Enable = void> struct formatting_range {
|
||||||
|
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||||
|
Char prefix = '{';
|
||||||
|
Char postfix = '}';
|
||||||
|
#else
|
||||||
|
Char prefix = '[';
|
||||||
|
Char postfix = ']';
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Enable = void>
|
template <typename Char, typename Enable = void> struct formatting_tuple {
|
||||||
struct formatting_range : formatting_base<Char> {
|
Char prefix = '(';
|
||||||
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
|
Char postfix = ')';
|
||||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
|
|
||||||
// range.
|
|
||||||
Char prefix;
|
|
||||||
Char delimiter;
|
|
||||||
Char postfix;
|
|
||||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char, typename Enable = void>
|
template <typename ParseContext>
|
||||||
struct formatting_tuple : formatting_base<Char> {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
Char prefix;
|
return ctx.begin();
|
||||||
Char delimiter;
|
}
|
||||||
Char postfix;
|
|
||||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
@ -75,8 +65,14 @@ OutputIterator copy(char ch, OutputIterator out) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename OutputIterator>
|
||||||
|
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||||
|
*out++ = ch;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/// Return true value if T has std::string interface, like std::string_view.
|
/// Return true value if T has std::string interface, like std::string_view.
|
||||||
template <typename T> class is_like_std_string {
|
template <typename T> class is_std_string_like {
|
||||||
template <typename U>
|
template <typename U>
|
||||||
static auto check(U* p)
|
static auto check(U* p)
|
||||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||||
|
@ -88,19 +84,107 @@ template <typename T> class is_like_std_string {
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||||
|
|
||||||
template <typename... Ts> struct conditional_helper {};
|
template <typename... Ts> struct conditional_helper {};
|
||||||
|
|
||||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||||
|
|
||||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||||
|
|
||||||
|
# define FMT_DECLTYPE_RETURN(val) \
|
||||||
|
->decltype(val) { return val; } \
|
||||||
|
static_assert( \
|
||||||
|
true, "") // This makes it so that a semicolon is required after the
|
||||||
|
// macro, which helps clang-format handle the formatting.
|
||||||
|
|
||||||
|
// C array overload
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
auto range_end(const T (&arr)[N]) -> const T* {
|
||||||
|
return arr + N;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_member_fn_begin_end_t : std::false_type {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_range_<
|
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
|
||||||
T, conditional_t<false,
|
decltype(std::declval<T>().end())>>
|
||||||
conditional_helper<decltype(std::declval<T>().begin()),
|
: std::true_type {};
|
||||||
decltype(std::declval<T>().end())>,
|
|
||||||
void>> : std::true_type {};
|
// Member function overload
|
||||||
|
template <typename T>
|
||||||
|
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||||
|
template <typename T>
|
||||||
|
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||||
|
|
||||||
|
// ADL overload. Only participates in overload resolution if member functions
|
||||||
|
// are not found.
|
||||||
|
template <typename T>
|
||||||
|
auto range_begin(T&& rng)
|
||||||
|
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||||
|
decltype(begin(static_cast<T&&>(rng)))> {
|
||||||
|
return begin(static_cast<T&&>(rng));
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||||
|
decltype(end(static_cast<T&&>(rng)))> {
|
||||||
|
return end(static_cast<T&&>(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_const_begin_end : std::false_type {};
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_mutable_begin_end : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_const_begin_end<
|
||||||
|
T, void_t<decltype(detail::range_begin(
|
||||||
|
std::declval<const remove_cvref_t<T>&>())),
|
||||||
|
decltype(detail::range_begin(
|
||||||
|
std::declval<const remove_cvref_t<T>&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_mutable_begin_end<
|
||||||
|
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||||
|
decltype(detail::range_begin(std::declval<T>())),
|
||||||
|
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_range_<T, void>
|
||||||
|
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||||
|
has_mutable_begin_end<T>::value)> {};
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void> struct range_to_view;
|
||||||
|
template <typename T>
|
||||||
|
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
|
||||||
|
struct view_t {
|
||||||
|
const T* m_range_ptr;
|
||||||
|
|
||||||
|
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
|
||||||
|
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
|
||||||
|
};
|
||||||
|
static auto view(const T& range) -> view_t { return {&range}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
|
||||||
|
has_mutable_begin_end<T>::value>> {
|
||||||
|
struct view_t {
|
||||||
|
T m_range_copy;
|
||||||
|
|
||||||
|
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
|
||||||
|
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
|
||||||
|
};
|
||||||
|
static auto view(const T& range) -> view_t { return {range}; }
|
||||||
|
};
|
||||||
|
# undef FMT_DECLTYPE_RETURN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// tuple_size and tuple_element check.
|
/// tuple_size and tuple_element check.
|
||||||
|
@ -157,30 +241,41 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
|
template <typename Range>
|
||||||
typename std::decay<Arg>::type>::value)>
|
using value_type =
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||||
return add_space ? " {}" : "{}";
|
|
||||||
|
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||||
|
*out++ = ',';
|
||||||
|
*out++ = ' ';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
|
template <
|
||||||
typename std::decay<Arg>::type>::value)>
|
typename Char, typename OutputIt, typename Arg,
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
|
||||||
return add_space ? " \"{}\"" : "\"{}\"";
|
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||||
|
*out++ = '"';
|
||||||
|
out = write<Char>(out, v);
|
||||||
|
*out++ = '"';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
template <typename Char, typename OutputIt, typename Arg,
|
||||||
return add_space ? " \"{}\"" : "\"{}\"";
|
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||||
}
|
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
*out++ = '\'';
|
||||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
*out++ = v;
|
||||||
|
*out++ = '\'';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
template <
|
||||||
return add_space ? " '{}'" : "'{}'";
|
typename Char, typename OutputIt, typename Arg,
|
||||||
}
|
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
!std::is_same<Arg, Char>::value)>
|
||||||
return add_space ? L" '{}'" : L"'{}'";
|
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||||
|
return write<Char>(out, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
@ -196,23 +291,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||||
// C++11 generic lambda for format()
|
// C++11 generic lambda for format()
|
||||||
template <typename FormatContext> struct format_each {
|
template <typename FormatContext> struct format_each {
|
||||||
template <typename T> void operator()(const T& v) {
|
template <typename T> void operator()(const T& v) {
|
||||||
if (i > 0) {
|
if (i > 0) out = detail::write_delimiter(out);
|
||||||
if (formatting.add_prepostfix_space) {
|
out = detail::write_range_entry<Char>(out, v);
|
||||||
*out++ = ' ';
|
|
||||||
}
|
|
||||||
out = detail::copy(formatting.delimiter, out);
|
|
||||||
}
|
|
||||||
out = format_to(out,
|
|
||||||
detail::format_str_quoted(
|
|
||||||
(formatting.add_delimiter_spaces && i > 0), v),
|
|
||||||
v);
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatting_tuple<Char>& formatting;
|
formatting_tuple<Char>& formatting;
|
||||||
size_t& i;
|
size_t& i;
|
||||||
typename std::add_lvalue_reference<decltype(
|
typename std::add_lvalue_reference<
|
||||||
std::declval<FormatContext>().out())>::type out;
|
decltype(std::declval<FormatContext>().out())>::type out;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -227,12 +313,9 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
detail::copy(formatting.prefix, out);
|
|
||||||
|
|
||||||
|
detail::copy(formatting.prefix, out);
|
||||||
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||||
if (formatting.add_prepostfix_space) {
|
|
||||||
*out++ = ' ';
|
|
||||||
}
|
|
||||||
detail::copy(formatting.postfix, out);
|
detail::copy(formatting.postfix, out);
|
||||||
|
|
||||||
return ctx.out();
|
return ctx.out();
|
||||||
|
@ -241,14 +324,22 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||||
|
|
||||||
template <typename T, typename Char> struct is_range {
|
template <typename T, typename Char> struct is_range {
|
||||||
static FMT_CONSTEXPR_DECL const bool value =
|
static FMT_CONSTEXPR_DECL const bool value =
|
||||||
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
|
detail::is_range_<T>::value && !detail::is_std_string_like<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_constructible<detail::std_string_view<Char>, T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename RangeT, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<RangeT, Char,
|
struct formatter<
|
||||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
|
T, Char,
|
||||||
|
enable_if_t<
|
||||||
|
fmt::is_range<T, Char>::value
|
||||||
|
// Workaround a bug in MSVC 2017 and earlier.
|
||||||
|
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
|
||||||
|
&& (has_formatter<detail::value_type<T>, format_context>::value ||
|
||||||
|
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||||
|
#endif
|
||||||
|
>> {
|
||||||
formatting_range<Char> formatting;
|
formatting_range<Char> formatting;
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
|
@ -257,75 +348,67 @@ struct formatter<RangeT, Char,
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
typename FormatContext::iterator format(const RangeT& values,
|
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
|
||||||
FormatContext& ctx) {
|
|
||||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
auto it = values.begin();
|
auto view = detail::range_to_view<T>::view(values);
|
||||||
auto end = values.end();
|
auto it = view.begin();
|
||||||
|
auto end = view.end();
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
if (i > 0) {
|
if (i > 0) out = detail::write_delimiter(out);
|
||||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
out = detail::write_range_entry<Char>(out, *it);
|
||||||
out = detail::copy(formatting.delimiter, out);
|
++i;
|
||||||
}
|
|
||||||
out = format_to(out,
|
|
||||||
detail::format_str_quoted(
|
|
||||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
|
||||||
*it);
|
|
||||||
if (++i > formatting.range_length_limit) {
|
|
||||||
out = format_to(out, " ... <other elements>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
|
||||||
return detail::copy(formatting.postfix, out);
|
return detail::copy(formatting.postfix, out);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
|
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||||
const std::tuple<T...>& tuple;
|
const std::tuple<T...>& tuple;
|
||||||
basic_string_view<Char> sep;
|
basic_string_view<Char> sep;
|
||||||
|
|
||||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||||
: tuple{t}, sep{s} {}
|
: tuple(t), sep{s} {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename... T>
|
template <typename Char, typename... T>
|
||||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||||
|
|
||||||
|
template <typename Char, typename... T>
|
||||||
|
struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
typename FormatContext::iterator format(
|
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
|
||||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
typename FormatContext::iterator {
|
||||||
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename FormatContext, size_t... N>
|
template <typename FormatContext, size_t... N>
|
||||||
typename FormatContext::iterator format(
|
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
detail::index_sequence<N...>) ->
|
||||||
detail::index_sequence<N...>) {
|
typename FormatContext::iterator {
|
||||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
typename FormatContext::iterator format_args(
|
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
|
||||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
typename FormatContext::iterator {
|
||||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||||
// can be replaced with a constexpr branch in the variadic overload.
|
// can be replaced with a constexpr branch in the variadic overload.
|
||||||
return ctx.out();
|
return ctx.out();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext, typename Arg, typename... Args>
|
template <typename FormatContext, typename Arg, typename... Args>
|
||||||
typename FormatContext::iterator format_args(
|
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
const Arg& arg, const Args&... args) ->
|
||||||
const Arg& arg, const Args&... args) {
|
typename FormatContext::iterator {
|
||||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||||
auto out = ctx.out();
|
auto out = base().format(arg, ctx);
|
||||||
out = base{}.format(arg, ctx);
|
|
||||||
if (sizeof...(Args) > 0) {
|
if (sizeof...(Args) > 0) {
|
||||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||||
ctx.advance_to(out);
|
ctx.advance_to(out);
|
||||||
|
@ -335,6 +418,8 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\rst
|
\rst
|
||||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||||
|
@ -347,14 +432,15 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||||
string_view sep) {
|
-> tuple_join_view<char, T...> {
|
||||||
return {tuple, sep};
|
return {tuple, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
|
||||||
wstring_view sep) {
|
basic_string_view<wchar_t> sep)
|
||||||
|
-> tuple_join_view<wchar_t, T...> {
|
||||||
return {tuple, sep};
|
return {tuple, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,17 +456,12 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
|
auto join(std::initializer_list<T> list, string_view sep)
|
||||||
string_view sep) {
|
-> join_view<const T*, const T*> {
|
||||||
return join(std::begin(list), std::end(list), sep);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
|
|
||||||
wstring_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_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_RANGES_H_
|
#endif // FMT_RANGES_H_
|
||||||
|
|
236
include/fmt/xchar.h
Normal file
236
include/fmt/xchar.h
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
// Formatting library for C++ - optional wchar_t and exotic character support
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FMT_WCHAR_H_
|
||||||
|
#define FMT_WCHAR_H_
|
||||||
|
|
||||||
|
#include <cwchar>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "format.h"
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
using wstring_view = basic_string_view<wchar_t>;
|
||||||
|
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||||
|
using wformat_context = buffer_context<wchar_t>;
|
||||||
|
using wformat_args = basic_format_args<wformat_context>;
|
||||||
|
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||||
|
|
||||||
|
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||||
|
// Workaround broken conversion on older gcc.
|
||||||
|
template <typename... Args> using wformat_string = wstring_view;
|
||||||
|
#else
|
||||||
|
template <typename... Args>
|
||||||
|
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <> struct is_char<wchar_t> : std::true_type {};
|
||||||
|
template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||||
|
template <> struct is_char<char16_t> : std::true_type {};
|
||||||
|
template <> struct is_char<char32_t> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||||
|
const Args&... args) {
|
||||||
|
return {args...};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline namespace literals {
|
||||||
|
constexpr auto operator"" _format(const wchar_t* s, size_t n)
|
||||||
|
-> detail::udl_formatter<wchar_t> {
|
||||||
|
return {{s, n}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
|
||||||
|
return {s};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace literals
|
||||||
|
|
||||||
|
template <typename It, typename Sentinel>
|
||||||
|
auto join(It begin, Sentinel end, wstring_view sep)
|
||||||
|
-> join_view<It, Sentinel, wchar_t> {
|
||||||
|
return {begin, end, sep};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Range>
|
||||||
|
auto join(Range&& range, wstring_view sep)
|
||||||
|
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
||||||
|
wchar_t> {
|
||||||
|
return join(std::begin(range), std::end(range), sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
auto join(std::initializer_list<T> list, wstring_view sep)
|
||||||
|
-> join_view<const T*, const T*, wchar_t> {
|
||||||
|
return join(std::begin(list), std::end(list), sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||||
|
auto vformat(basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
|
basic_memory_buffer<Char> buffer;
|
||||||
|
detail::vformat_to(buffer, format_str, args);
|
||||||
|
return to_string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass char_t as a default template parameter instead of using
|
||||||
|
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||||
|
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||||
|
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||||
|
return vformat(to_string_view(format_str), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale, typename S, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto vformat(
|
||||||
|
const Locale& loc, const S& format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
|
return detail::vformat(loc, to_string_view(format_str), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
|
return detail::vformat(loc, to_string_view(format_str),
|
||||||
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename S, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
auto vformat_to(OutputIt out, const S& format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> OutputIt {
|
||||||
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
|
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||||
|
return detail::get_iterator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||||
|
return vformat_to(out, to_string_view(fmt), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args, typename Char, size_t SIZE,
|
||||||
|
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
|
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
|
||||||
|
const S& format_str, Args&&... args) ->
|
||||||
|
typename buffer_context<Char>::iterator {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||||
|
detail::vformat_to(buf, to_string_view(format_str), vargs);
|
||||||
|
return detail::buffer_appender<Char>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_locale<Locale>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto vformat_to(
|
||||||
|
OutputIt out, const Locale& loc, const S& format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
|
||||||
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
|
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
|
||||||
|
return detail::get_iterator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
bool enable = detail::is_output_iterator<OutputIt, 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,
|
||||||
|
Args&&... args) ->
|
||||||
|
typename std::enable_if<enable, OutputIt>::type {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||||
|
return vformat_to(out, loc, to_string_view(format_str), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename Char, typename... Args,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto vformat_to_n(
|
||||||
|
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> format_to_n_result<OutputIt> {
|
||||||
|
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||||
|
n);
|
||||||
|
detail::vformat_to(buf, format_str, args);
|
||||||
|
return {buf.out(), buf.count()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||||
|
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||||
|
return vformat_to_n(out, n, to_string_view(fmt), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||||
|
detail::counting_buffer<Char> buf;
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||||
|
detail::vformat_to(buf, to_string_view(fmt), vargs);
|
||||||
|
return buf.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||||
|
wmemory_buffer buffer;
|
||||||
|
detail::vformat_to(buffer, fmt, args);
|
||||||
|
buffer.push_back(L'\0');
|
||||||
|
if (std::fputws(buffer.data(), f) == -1)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vprint(wstring_view fmt, wformat_args args) {
|
||||||
|
vprint(stdout, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||||
|
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||||
|
return vprint(wstring_view(fmt), make_wformat_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||||
|
*/
|
||||||
|
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||||
|
return format(FMT_STRING(L"{}"), value);
|
||||||
|
}
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // FMT_WCHAR_H_
|
100
src/fmt.cc
Normal file
100
src/fmt.cc
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <chrono>
|
||||||
|
#include <climits>
|
||||||
|
#include <clocale>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cwchar>
|
||||||
|
#include <exception>
|
||||||
|
#include <functional>
|
||||||
|
#include <iterator>
|
||||||
|
#include <limits>
|
||||||
|
#include <locale>
|
||||||
|
#include <memory>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <system_error>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if _MSC_VER
|
||||||
|
# include <intrin.h>
|
||||||
|
#endif
|
||||||
|
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||||
|
# include <xlocale.h>
|
||||||
|
#endif
|
||||||
|
#if __has_include(<winapifamily.h>)
|
||||||
|
# include <winapifamily.h>
|
||||||
|
#endif
|
||||||
|
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
|
||||||
|
defined(__linux__)) && \
|
||||||
|
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||||
|
# include <fcntl.h>
|
||||||
|
# include <sys/stat.h>
|
||||||
|
# include <sys/types.h>
|
||||||
|
# ifndef _WIN32
|
||||||
|
# include <unistd.h>
|
||||||
|
# else
|
||||||
|
# include <io.h>
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
export module fmt;
|
||||||
|
|
||||||
|
#define FMT_MODULE_EXPORT export
|
||||||
|
#define FMT_MODULE_EXPORT_BEGIN export {
|
||||||
|
#define FMT_MODULE_EXPORT_END }
|
||||||
|
#define FMT_BEGIN_DETAIL_NAMESPACE \
|
||||||
|
} \
|
||||||
|
namespace detail {
|
||||||
|
#define FMT_END_DETAIL_NAMESPACE \
|
||||||
|
} \
|
||||||
|
export {
|
||||||
|
|
||||||
|
// all library-provided declarations and definitions
|
||||||
|
// must be in the module purview to be exported
|
||||||
|
#include "fmt/args.h"
|
||||||
|
#include "fmt/chrono.h"
|
||||||
|
#include "fmt/color.h"
|
||||||
|
#include "fmt/compile.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
#include "fmt/os.h"
|
||||||
|
#include "fmt/printf.h"
|
||||||
|
#include "fmt/xchar.h"
|
||||||
|
|
||||||
|
// gcc doesn't yet implement private module fragments
|
||||||
|
#if !FMT_GCC_VERSION
|
||||||
|
module : private;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "format.cc"
|
||||||
|
#include "os.cc"
|
|
@ -23,9 +23,12 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||||
: snprintf_ptr(buf, size, format, precision, value);
|
: snprintf_ptr(buf, size, format, precision, value);
|
||||||
}
|
}
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
|
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
|
||||||
|
FMT_NOEXCEPT;
|
||||||
|
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
|
||||||
|
FMT_NOEXCEPT;
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||||
int (*instantiate_format_float)(double, int, detail::float_specs,
|
int (*instantiate_format_float)(double, int, detail::float_specs,
|
||||||
|
@ -38,15 +41,15 @@ template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||||
|
|
||||||
// Explicit instantiations for char.
|
// Explicit instantiations for char.
|
||||||
|
|
||||||
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
|
template FMT_API auto detail::thousands_sep_impl(locale_ref)
|
||||||
template FMT_API char detail::thousands_sep_impl(locale_ref);
|
-> thousands_sep_result<char>;
|
||||||
template FMT_API char detail::decimal_point_impl(locale_ref);
|
template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||||
|
|
||||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||||
|
|
||||||
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
|
template FMT_API void detail::vformat_to(
|
||||||
detail::buffer<char>&, string_view,
|
detail::buffer<char>&, string_view,
|
||||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
|
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||||
|
|
||||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||||
detail::buffer<char>&);
|
detail::buffer<char>&);
|
||||||
|
@ -60,10 +63,13 @@ template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
// Explicit instantiations for wchar_t.
|
||||||
|
|
||||||
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
|
template FMT_API auto detail::thousands_sep_impl(locale_ref)
|
||||||
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
|
-> thousands_sep_result<wchar_t>;
|
||||||
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
||||||
|
|
||||||
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
||||||
const wchar_t*);
|
const wchar_t*);
|
||||||
|
|
||||||
|
template struct detail::basic_data<void>;
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
127
src/os.cc
127
src/os.cc
|
@ -25,7 +25,6 @@
|
||||||
# define WIN32_LEAN_AND_MEAN
|
# define WIN32_LEAN_AND_MEAN
|
||||||
# endif
|
# endif
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
# include <windows.h>
|
|
||||||
|
|
||||||
# define O_CREAT _O_CREAT
|
# define O_CREAT _O_CREAT
|
||||||
# define O_TRUNC _O_TRUNC
|
# define O_TRUNC _O_TRUNC
|
||||||
|
@ -55,16 +54,16 @@
|
||||||
namespace {
|
namespace {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Return type of read and write functions.
|
// Return type of read and write functions.
|
||||||
using RWResult = int;
|
using rwresult = int;
|
||||||
|
|
||||||
// On Windows the count argument to read and write is unsigned, so convert
|
// On Windows the count argument to read and write is unsigned, so convert
|
||||||
// it from size_t preventing integer overflow.
|
// it from size_t preventing integer overflow.
|
||||||
inline unsigned convert_rwcount(std::size_t count) {
|
inline unsigned convert_rwcount(std::size_t count) {
|
||||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||||
}
|
}
|
||||||
#else
|
#elif FMT_USE_FCNTL
|
||||||
// Return type of read and write functions.
|
// Return type of read and write functions.
|
||||||
using RWResult = ssize_t;
|
using rwresult = ssize_t;
|
||||||
|
|
||||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||||
#endif
|
#endif
|
||||||
|
@ -73,14 +72,14 @@ 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(wstring_view s) {
|
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
|
||||||
if (int error_code = convert(s)) {
|
if (int error_code = convert(s)) {
|
||||||
FMT_THROW(windows_error(error_code,
|
FMT_THROW(windows_error(error_code,
|
||||||
"cannot convert string from UTF-16 to UTF-8"));
|
"cannot convert string from UTF-16 to UTF-8"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int detail::utf16_to_utf8::convert(wstring_view s) {
|
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
|
||||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||||
int s_size = static_cast<int>(s.size());
|
int s_size = static_cast<int>(s.size());
|
||||||
if (s_size == 0) {
|
if (s_size == 0) {
|
||||||
|
@ -101,45 +100,85 @@ int detail::utf16_to_utf8::convert(wstring_view s) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void windows_error::init(int err_code, string_view format_str,
|
namespace detail {
|
||||||
format_args args) {
|
|
||||||
error_code_ = err_code;
|
class system_message {
|
||||||
memory_buffer buffer;
|
system_message(const system_message&) = delete;
|
||||||
detail::format_windows_error(buffer, err_code, vformat(format_str, args));
|
void operator=(const system_message&) = delete;
|
||||||
std::runtime_error& base = *this;
|
|
||||||
base = std::runtime_error(to_string(buffer));
|
unsigned long result_;
|
||||||
|
wchar_t* message_;
|
||||||
|
|
||||||
|
static bool is_whitespace(wchar_t c) FMT_NOEXCEPT {
|
||||||
|
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit system_message(unsigned long error_code)
|
||||||
|
: result_(0), message_(nullptr) {
|
||||||
|
result_ = FormatMessageW(
|
||||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
|
||||||
|
if (result_ != 0) {
|
||||||
|
while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
|
||||||
|
--result_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~system_message() { LocalFree(message_); }
|
||||||
|
explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; }
|
||||||
|
operator basic_string_view<wchar_t>() const FMT_NOEXCEPT {
|
||||||
|
return basic_string_view<wchar_t>(message_, result_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class utf8_system_category final : public std::error_category {
|
||||||
|
public:
|
||||||
|
const char* name() const FMT_NOEXCEPT override { return "system"; }
|
||||||
|
std::string message(int error_code) const override {
|
||||||
|
system_message msg(error_code);
|
||||||
|
if (msg) {
|
||||||
|
utf16_to_utf8 utf8_message;
|
||||||
|
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||||
|
return utf8_message.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown error";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_API const std::error_category& system_category() FMT_NOEXCEPT {
|
||||||
|
static const detail::utf8_system_category category;
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::system_error vwindows_error(int err_code, string_view format_str,
|
||||||
|
format_args args) {
|
||||||
|
auto ec = std::error_code(err_code, system_category());
|
||||||
|
return std::system_error(ec, vformat(format_str, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||||
string_view message) FMT_NOEXCEPT {
|
const char* message) FMT_NOEXCEPT {
|
||||||
FMT_TRY {
|
FMT_TRY {
|
||||||
wmemory_buffer buf;
|
system_message msg(error_code);
|
||||||
buf.resize(inline_buffer_size);
|
if (msg) {
|
||||||
for (;;) {
|
utf16_to_utf8 utf8_message;
|
||||||
wchar_t* system_message = &buf[0];
|
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||||
int result = FormatMessageW(
|
format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
|
||||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
|
return;
|
||||||
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message,
|
|
||||||
static_cast<uint32_t>(buf.size()), nullptr);
|
|
||||||
if (result != 0) {
|
|
||||||
utf16_to_utf8 utf8_message;
|
|
||||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
|
||||||
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
|
||||||
break; // Can't get error message, report error code instead.
|
|
||||||
buf.resize(buf.size() * 2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FMT_CATCH(...) {}
|
FMT_CATCH(...) {}
|
||||||
format_error_code(out, error_code, message);
|
format_error_code(out, error_code, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void report_windows_error(int error_code,
|
void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT {
|
||||||
fmt::string_view message) FMT_NOEXCEPT {
|
|
||||||
report_error(detail::format_windows_error, error_code, message);
|
report_error(detail::format_windows_error, error_code, message);
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
@ -228,14 +267,14 @@ 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, "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, "cannot write to file"));
|
||||||
return detail::to_unsigned(result);
|
return detail::to_unsigned(result);
|
||||||
|
@ -259,10 +298,10 @@ void file::dup2(int fd) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT {
|
void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT {
|
||||||
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) ec = error_code(errno);
|
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void file::pipe(file& read_end, file& write_end) {
|
void file::pipe(file& read_end, file& write_end) {
|
||||||
|
@ -288,12 +327,12 @@ void file::pipe(file& read_end, file& write_end) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffered_file file::fdopen(const char* mode) {
|
buffered_file file::fdopen(const char* mode) {
|
||||||
// Don't retry as fdopen doesn't return EINTR.
|
// Don't retry as fdopen doesn't return EINTR.
|
||||||
#if defined(__MINGW32__) && defined(_POSIX_)
|
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||||
FILE* f = ::fdopen(fd_, mode);
|
FILE* f = ::fdopen(fd_, 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(errno, "cannot associate stream with file descriptor"));
|
system_error(errno, "cannot associate stream with file descriptor"));
|
||||||
|
@ -313,5 +352,9 @@ long getpagesize() {
|
||||||
return size;
|
return size;
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FMT_API void ostream::grow(size_t) {
|
||||||
|
if (this->size() == this->capacity()) flush();
|
||||||
|
}
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
|
@ -2,5 +2,3 @@ This directory contains build support files such as
|
||||||
|
|
||||||
* CMake modules
|
* CMake modules
|
||||||
* Build scripts
|
* Build scripts
|
||||||
* qmake (static build with dynamic libc only)
|
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,8 @@ clone_depth: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
- Visual Studio 2015
|
- Visual Studio 2015
|
||||||
- Visual Studio 2019
|
|
||||||
- Visual Studio 2017
|
|
||||||
|
|
||||||
platform:
|
platform:
|
||||||
- Win32
|
|
||||||
- x64
|
- x64
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -18,13 +15,6 @@ environment:
|
||||||
MSVC_DEFAULT_OPTIONS: ON
|
MSVC_DEFAULT_OPTIONS: ON
|
||||||
BUILD: msvc
|
BUILD: msvc
|
||||||
|
|
||||||
matrix:
|
|
||||||
exclude:
|
|
||||||
- image: Visual Studio 2015
|
|
||||||
platform: Win32
|
|
||||||
- image: Visual Studio 2019
|
|
||||||
platform: Win32
|
|
||||||
|
|
||||||
before_build:
|
before_build:
|
||||||
- mkdir build
|
- mkdir build
|
||||||
- cd build
|
- cd build
|
||||||
|
|
58
support/build-docs.py
Executable file
58
support/build-docs.py
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Build the documentation in CI.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import errno, os, shutil, subprocess, sys, urllib
|
||||||
|
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||||
|
|
||||||
|
def rmtree_if_exists(dir):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(dir)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Build the docs.
|
||||||
|
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
||||||
|
import build
|
||||||
|
build.create_build_env()
|
||||||
|
html_dir = build.build_docs()
|
||||||
|
|
||||||
|
repo = 'fmtlib.github.io'
|
||||||
|
branch = os.environ['GITHUB_REF']
|
||||||
|
is_ci = 'CI' in os.environ
|
||||||
|
if is_ci and branch != 'refs/heads/master':
|
||||||
|
print('Branch: ' + branch)
|
||||||
|
exit(0) # Ignore non-master branches
|
||||||
|
if is_ci and 'KEY' not in os.environ:
|
||||||
|
# Don't update the repo if building in CI from an account that doesn't have
|
||||||
|
# push access.
|
||||||
|
print('Skipping update of ' + repo)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
# Clone the fmtlib.github.io repo.
|
||||||
|
rmtree_if_exists(repo)
|
||||||
|
git_url = 'https://github.com/' if is_ci else 'git@github.com:'
|
||||||
|
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
||||||
|
|
||||||
|
# Copy docs to the repo.
|
||||||
|
target_dir = os.path.join(repo, 'dev')
|
||||||
|
rmtree_if_exists(target_dir)
|
||||||
|
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
||||||
|
if is_ci:
|
||||||
|
check_call(['git', 'config', '--global', 'user.name', 'fmtbot'])
|
||||||
|
check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev'])
|
||||||
|
|
||||||
|
# Push docs to GitHub pages.
|
||||||
|
check_call(['git', 'add', '--all'], cwd=repo)
|
||||||
|
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
||||||
|
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
||||||
|
cmd = 'git push'
|
||||||
|
if is_ci:
|
||||||
|
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
||||||
|
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
||||||
|
# Print the output without the key.
|
||||||
|
print(p.communicate()[0].decode('utf-8').replace(os.environ['KEY'], '$KEY'))
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(p.returncode, cmd)
|
|
@ -1,3 +1,4 @@
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
// General gradle arguments for root project
|
// General gradle arguments for root project
|
||||||
buildscript {
|
buildscript {
|
||||||
|
@ -7,24 +8,25 @@ buildscript {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//
|
//
|
||||||
// https://developer.android.com/studio/releases/gradle-plugin
|
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
||||||
//
|
//
|
||||||
// Notice that 3.3.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 5.0 or higher
|
// Accroding to URL above you will need Gradle 6.1 or higher
|
||||||
//
|
//
|
||||||
// If you are using Android Studio, and it is using Gradle's lower
|
classpath "com.android.tools.build:gradle:4.1.1"
|
||||||
// version, Use the plugin version 3.1.3 ~ 3.2.0 for Gradle 4.4 ~ 4.10
|
|
||||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output: Shared library (.so) for Android
|
|
||||||
apply plugin: 'com.android.library'
|
|
||||||
|
|
||||||
|
// Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir
|
||||||
|
def rootDir = Paths.get(project.buildDir.getParent()).getParent()
|
||||||
|
println("rootDir: ${rootDir}")
|
||||||
|
|
||||||
|
// Output: Shared library (.so) for Android
|
||||||
|
apply plugin: "com.android.library"
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25 // Android 7.0
|
compileSdkVersion 25 // Android 7.0
|
||||||
|
|
||||||
|
@ -41,13 +43,13 @@ android {
|
||||||
include "arm64-v8a", "armeabi-v7a", "x86_64"
|
include "arm64-v8a", "armeabi-v7a", "x86_64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21 // Android 5.0+
|
minSdkVersion 21 // Android 5.0+
|
||||||
targetSdkVersion 25 // Follow Compile SDK
|
targetSdkVersion 25 // Follow Compile SDK
|
||||||
versionCode 21 // Follow release count
|
versionCode 34 // Follow release count
|
||||||
versionName "5.3.0" // Follow Official version
|
versionName "7.1.2" // Follow Official version
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
|
@ -56,9 +58,9 @@ android {
|
||||||
arguments "-DFMT_TEST=false" // Skip test
|
arguments "-DFMT_TEST=false" // Skip test
|
||||||
arguments "-DFMT_DOC=false" // Skip document
|
arguments "-DFMT_DOC=false" // Skip document
|
||||||
cppFlags "-std=c++17"
|
cppFlags "-std=c++17"
|
||||||
|
targets "fmt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("Gradle CMake Plugin: ")
|
|
||||||
println(externalNativeBuild.cmake.cppFlags)
|
println(externalNativeBuild.cmake.cppFlags)
|
||||||
println(externalNativeBuild.cmake.arguments)
|
println(externalNativeBuild.cmake.arguments)
|
||||||
}
|
}
|
||||||
|
@ -69,16 +71,27 @@ android {
|
||||||
// neighbor of the top level cmake
|
// neighbor of the top level cmake
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path "../CMakeLists.txt"
|
version "3.10.0+"
|
||||||
|
path "${rootDir}/CMakeLists.txt"
|
||||||
// buildStagingDirectory "./build" // Custom path for cmake output
|
// buildStagingDirectory "./build" // Custom path for cmake output
|
||||||
}
|
}
|
||||||
//println(cmake.path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets{
|
sourceSets{
|
||||||
// Android Manifest for Gradle
|
// Android Manifest for Gradle
|
||||||
main {
|
main {
|
||||||
manifest.srcFile 'AndroidManifest.xml'
|
manifest.srcFile "AndroidManifest.xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.android.com/studio/build/native-dependencies#build_system_configuration
|
||||||
|
buildFeatures {
|
||||||
|
prefab true
|
||||||
|
prefabPublishing true
|
||||||
|
}
|
||||||
|
prefab {
|
||||||
|
fmt {
|
||||||
|
headers "${rootDir}/include"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,20 +101,32 @@ assemble.doLast
|
||||||
// Instead of `ninja install`, Gradle will deploy the files.
|
// Instead of `ninja install`, Gradle will deploy the files.
|
||||||
// We are doing this since FMT is dependent to the ANDROID_STL after build
|
// We are doing this since FMT is dependent to the ANDROID_STL after build
|
||||||
copy {
|
copy {
|
||||||
from 'build/intermediates/cmake'
|
from "build/intermediates/cmake"
|
||||||
into '../libs'
|
into "${rootDir}/libs"
|
||||||
}
|
}
|
||||||
// Copy debug binaries
|
// Copy debug binaries
|
||||||
copy {
|
copy {
|
||||||
from '../libs/debug/obj'
|
from "${rootDir}/libs/debug/obj"
|
||||||
into '../libs/debug'
|
into "${rootDir}/libs/debug"
|
||||||
}
|
}
|
||||||
// Copy Release binaries
|
// Copy Release binaries
|
||||||
copy {
|
copy {
|
||||||
from '../libs/release/obj'
|
from "${rootDir}/libs/release/obj"
|
||||||
into '../libs/release'
|
into "${rootDir}/libs/release"
|
||||||
}
|
}
|
||||||
// Remove empty directory
|
// Remove empty directory
|
||||||
delete '../libs/debug/obj'
|
delete "${rootDir}/libs/debug/obj"
|
||||||
delete '../libs/release/obj'
|
delete "${rootDir}/libs/release/obj"
|
||||||
|
|
||||||
|
// Copy AAR files. Notice that the aar is named after the folder of this script.
|
||||||
|
copy {
|
||||||
|
from "build/outputs/aar/support-release.aar"
|
||||||
|
into "${rootDir}/libs"
|
||||||
|
rename "support-release.aar", "fmt-release.aar"
|
||||||
|
}
|
||||||
|
copy {
|
||||||
|
from "build/outputs/aar/support-debug.aar"
|
||||||
|
into "${rootDir}/libs"
|
||||||
|
rename "support-debug.aar", "fmt-debug.aar"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
# Staticlib configuration for qmake builds
|
|
||||||
# For some reason qmake 3.1 fails to identify source dependencies and excludes format.cc and printf.cc
|
|
||||||
# from compilation so it _MUST_ be called as qmake -nodepend
|
|
||||||
# A workaround is implemented below: a custom compiler is defined which does not track dependencies
|
|
||||||
|
|
||||||
TEMPLATE = lib
|
|
||||||
|
|
||||||
TARGET = fmt
|
|
||||||
|
|
||||||
QMAKE_EXT_CPP = .cc
|
|
||||||
|
|
||||||
CONFIG = staticlib warn_on c++11
|
|
||||||
|
|
||||||
FMT_SOURCES = \
|
|
||||||
../src/format.cc \
|
|
||||||
../src/posix.cc
|
|
||||||
|
|
||||||
fmt.name = libfmt
|
|
||||||
fmt.input = FMT_SOURCES
|
|
||||||
fmt.output = ${QMAKE_FILE_BASE}$$QMAKE_EXT_OBJ
|
|
||||||
fmt.clean = ${QMAKE_FILE_BASE}$$QMAKE_EXT_OBJ
|
|
||||||
fmt.depends = ${QMAKE_FILE_IN}
|
|
||||||
# QMAKE_RUN_CXX will not be expanded
|
|
||||||
fmt.commands = $$QMAKE_CXX -c $$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_WARN_ON $$QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO $$QMAKE_CXXFLAGS_CXX11 ${QMAKE_FILE_IN}
|
|
||||||
fmt.variable_out = OBJECTS
|
|
||||||
fmt.CONFIG = no_dependencies no_link
|
|
||||||
QMAKE_EXTRA_COMPILERS += fmt
|
|
|
@ -65,7 +65,7 @@ class Translator(nodes.NodeVisitor):
|
||||||
self.write('\n\n')
|
self.write('\n\n')
|
||||||
|
|
||||||
def visit_paragraph(self, node):
|
def visit_paragraph(self, node):
|
||||||
pass
|
self.write('\n\n')
|
||||||
|
|
||||||
def depart_paragraph(self, node):
|
def depart_paragraph(self, node):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# Build the project on Travis CI.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
import errno, os, shutil, subprocess, sys, urllib
|
|
||||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
|
||||||
|
|
||||||
def rmtree_if_exists(dir):
|
|
||||||
try:
|
|
||||||
shutil.rmtree(dir)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.ENOENT:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def makedirs_if_not_exist(dir):
|
|
||||||
try:
|
|
||||||
os.makedirs(dir)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno != errno.EEXIST:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def install_dependencies():
|
|
||||||
branch = os.environ['TRAVIS_BRANCH']
|
|
||||||
if branch != 'master':
|
|
||||||
print('Branch: ' + branch)
|
|
||||||
exit(0) # Ignore non-master branches
|
|
||||||
check_call('curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key ' +
|
|
||||||
'| sudo apt-key add -', shell=True)
|
|
||||||
check_call('echo "deb https://deb.nodesource.com/node_0.10 precise main" ' +
|
|
||||||
'| sudo tee /etc/apt/sources.list.d/nodesource.list', shell=True)
|
|
||||||
check_call(['sudo', 'apt-get', 'update'])
|
|
||||||
check_call(['sudo', 'apt-get', 'install', 'python-virtualenv', 'nodejs'])
|
|
||||||
check_call(['sudo', 'npm', 'install', '-g', 'less@2.6.1', 'less-plugin-clean-css'])
|
|
||||||
deb_file = 'doxygen_1.8.6-2_amd64.deb'
|
|
||||||
urllib.urlretrieve('http://mirrors.kernel.org/ubuntu/pool/main/d/doxygen/' +
|
|
||||||
deb_file, deb_file)
|
|
||||||
check_call(['sudo', 'dpkg', '-i', deb_file])
|
|
||||||
|
|
||||||
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
|
|
||||||
build = os.environ['BUILD']
|
|
||||||
if build == 'Doc':
|
|
||||||
travis = 'TRAVIS' in os.environ
|
|
||||||
if travis:
|
|
||||||
install_dependencies()
|
|
||||||
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
|
||||||
import build
|
|
||||||
build.create_build_env()
|
|
||||||
html_dir = build.build_docs()
|
|
||||||
repo = 'fmtlib.github.io'
|
|
||||||
if travis and 'KEY' not in os.environ:
|
|
||||||
# Don't update the repo if building on Travis from an account that
|
|
||||||
# doesn't have push access.
|
|
||||||
print('Skipping update of ' + repo)
|
|
||||||
exit(0)
|
|
||||||
# Clone the fmtlib.github.io repo.
|
|
||||||
rmtree_if_exists(repo)
|
|
||||||
git_url = 'https://github.com/' if travis else 'git@github.com:'
|
|
||||||
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
|
||||||
# Copy docs to the repo.
|
|
||||||
target_dir = os.path.join(repo, 'dev')
|
|
||||||
rmtree_if_exists(target_dir)
|
|
||||||
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
|
||||||
if travis:
|
|
||||||
check_call(['git', 'config', '--global', 'user.name', 'amplbot'])
|
|
||||||
check_call(['git', 'config', '--global', 'user.email', 'viz@ampl.com'])
|
|
||||||
# Push docs to GitHub pages.
|
|
||||||
check_call(['git', 'add', '--all'], cwd=repo)
|
|
||||||
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
|
||||||
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
|
||||||
cmd = 'git push'
|
|
||||||
if travis:
|
|
||||||
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
|
||||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
|
||||||
# Print the output without the key.
|
|
||||||
print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
|
|
||||||
if p.returncode != 0:
|
|
||||||
raise subprocess.CalledProcessError(p.returncode, cmd)
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
standard = os.environ['STANDARD']
|
|
||||||
install_dir = os.path.join(fmt_dir, "_install")
|
|
||||||
build_dir = os.path.join(fmt_dir, "_build")
|
|
||||||
test_build_dir = os.path.join(fmt_dir, "_build_test")
|
|
||||||
|
|
||||||
# Configure the library.
|
|
||||||
makedirs_if_not_exist(build_dir)
|
|
||||||
cmake_flags = [
|
|
||||||
'-DCMAKE_INSTALL_PREFIX=' + install_dir, '-DCMAKE_BUILD_TYPE=' + build,
|
|
||||||
'-DCMAKE_CXX_STANDARD=' + standard
|
|
||||||
]
|
|
||||||
|
|
||||||
# Make sure the fuzzers still compile.
|
|
||||||
main_cmake_flags = list(cmake_flags)
|
|
||||||
if 'ENABLE_FUZZING' in os.environ:
|
|
||||||
main_cmake_flags += ['-DFMT_FUZZ=ON', '-DFMT_FUZZ_LINKMAIN=On']
|
|
||||||
|
|
||||||
check_call(['cmake', '-DFMT_DOC=OFF', '-DFMT_PEDANTIC=ON', '-DFMT_WERROR=ON', fmt_dir] +
|
|
||||||
main_cmake_flags, cwd=build_dir)
|
|
||||||
|
|
||||||
# Build the library.
|
|
||||||
check_call(['cmake', '--build','.'], cwd=build_dir)
|
|
||||||
|
|
||||||
# Test the library.
|
|
||||||
env = os.environ.copy()
|
|
||||||
env['CTEST_OUTPUT_ON_FAILURE'] = '1'
|
|
||||||
if call(['make', 'test'], env=env, cwd=build_dir):
|
|
||||||
with open(os.path.join(build_dir, 'Testing', 'Temporary', 'LastTest.log'), 'r') as f:
|
|
||||||
print(f.read())
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
# Install the library.
|
|
||||||
check_call(['make', 'install'], cwd=build_dir)
|
|
||||||
|
|
||||||
# Test installation.
|
|
||||||
makedirs_if_not_exist(test_build_dir)
|
|
||||||
check_call(['cmake', os.path.join(fmt_dir, "test", "find-package-test")] +
|
|
||||||
cmake_flags, cwd=test_build_dir)
|
|
||||||
check_call(['make', '-j4'], cwd=test_build_dir)
|
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# Update the coverity branch from the master branch.
|
|
||||||
# It is not done automatically because Coverity Scan limits
|
|
||||||
# the number of submissions per day.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
import shutil, tempfile
|
|
||||||
from subprocess import check_output, STDOUT
|
|
||||||
|
|
||||||
class Git:
|
|
||||||
def __init__(self, dir):
|
|
||||||
self.dir = dir
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
output = check_output(['git'] + list(args), cwd=self.dir, stderr=STDOUT)
|
|
||||||
print(output)
|
|
||||||
return output
|
|
||||||
|
|
||||||
dir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
git = Git(dir)
|
|
||||||
git('clone', '-b', 'coverity', 'git@github.com:fmtlib/fmt.git', dir)
|
|
||||||
output = git('merge', '-X', 'theirs', '--no-commit', 'origin/master')
|
|
||||||
if 'Fast-forward' not in output:
|
|
||||||
git('reset', 'HEAD', '.travis.yml')
|
|
||||||
git('checkout', '--', '.travis.yml')
|
|
||||||
git('commit', '-m', 'Update coverity branch')
|
|
||||||
git('push')
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(dir)
|
|
|
@ -1,52 +1,10 @@
|
||||||
#------------------------------------------------------------------------------
|
add_subdirectory(gtest)
|
||||||
# Build the google test library
|
|
||||||
|
|
||||||
# We compile Google Test ourselves instead of using pre-compiled libraries.
|
|
||||||
# See the Google Test FAQ "Why is it not recommended to install a
|
|
||||||
# pre-compiled copy of Google Test (for example, into /usr/local)?"
|
|
||||||
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
|
|
||||||
add_library(gmock STATIC
|
|
||||||
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
|
|
||||||
target_compile_definitions(gmock PUBLIC GTEST_HAS_STD_WSTRING=1)
|
|
||||||
target_include_directories(gmock SYSTEM PUBLIC . gmock gtest)
|
|
||||||
|
|
||||||
find_package(Threads)
|
|
||||||
if (Threads_FOUND)
|
|
||||||
target_link_libraries(gmock ${CMAKE_THREAD_LIBS_INIT})
|
|
||||||
else ()
|
|
||||||
target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
|
|
||||||
|
|
||||||
if (MSVC)
|
|
||||||
# Workaround a bug in implementation of variadic templates in MSVC11.
|
|
||||||
target_compile_definitions(gmock PUBLIC _VARIADIC_MAX=10)
|
|
||||||
|
|
||||||
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
|
||||||
target_compile_definitions(gmock PRIVATE _CRT_SECURE_NO_WARNINGS)
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
# Disable MSVC warnings of POSIX functions.
|
|
||||||
target_compile_options(gmock PUBLIC -Wno-deprecated-declarations)
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# GTest doesn't detect <tuple> with clang.
|
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
target_compile_definitions(gmock PUBLIC GTEST_USE_OWN_TR1_TUPLE=1)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Silence MSVC tr1 deprecation warning in gmock.
|
|
||||||
target_compile_definitions(gmock
|
|
||||||
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# Build the actual library tests
|
|
||||||
|
|
||||||
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 SYSTEM PUBLIC gtest gmock)
|
target_include_directories(test-main PUBLIC
|
||||||
target_link_libraries(test-main gmock fmt)
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||||
|
target_link_libraries(test-main gtest)
|
||||||
|
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
|
|
||||||
|
@ -73,8 +31,23 @@ endfunction()
|
||||||
# Adds a test.
|
# Adds a test.
|
||||||
# Usage: add_fmt_test(name srcs...)
|
# Usage: add_fmt_test(name srcs...)
|
||||||
function(add_fmt_test name)
|
function(add_fmt_test name)
|
||||||
add_fmt_executable(${name} ${name}.cc ${ARGN})
|
cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY;MODULE" "" "" ${ARGN})
|
||||||
target_link_libraries(${name} test-main)
|
|
||||||
|
set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS})
|
||||||
|
if (ADD_FMT_TEST_HEADER_ONLY)
|
||||||
|
set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc)
|
||||||
|
set(libs gtest fmt-header-only)
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
|
||||||
|
endif ()
|
||||||
|
elseif (ADD_FMT_TEST_MODULE)
|
||||||
|
set(libs test-main test-module)
|
||||||
|
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
|
||||||
|
else ()
|
||||||
|
set(libs test-main fmt)
|
||||||
|
endif ()
|
||||||
|
add_fmt_executable(${name} ${sources})
|
||||||
|
target_link_libraries(${name} ${libs})
|
||||||
|
|
||||||
# Define if certain C++ features can be used.
|
# Define if certain C++ features can be used.
|
||||||
if (FMT_PEDANTIC)
|
if (FMT_PEDANTIC)
|
||||||
|
@ -83,39 +56,71 @@ function(add_fmt_test name)
|
||||||
if (FMT_WERROR)
|
if (FMT_WERROR)
|
||||||
target_compile_options(${name} PRIVATE ${WERROR_FLAG})
|
target_compile_options(${name} PRIVATE ${WERROR_FLAG})
|
||||||
endif ()
|
endif ()
|
||||||
target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
|
|
||||||
add_test(NAME ${name} COMMAND ${name})
|
add_test(NAME ${name} COMMAND ${name})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
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)
|
||||||
add_fmt_test(color-test)
|
add_fmt_test(color-test)
|
||||||
add_fmt_test(core-test)
|
add_fmt_test(core-test)
|
||||||
add_fmt_test(grisu-test)
|
|
||||||
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
|
|
||||||
add_fmt_test(gtest-extra-test)
|
add_fmt_test(gtest-extra-test)
|
||||||
add_fmt_test(format-test mock-allocator.h)
|
add_fmt_test(format-test mock-allocator.h)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_options(format-test PRIVATE /bigobj)
|
target_compile_options(format-test PRIVATE /bigobj)
|
||||||
endif ()
|
endif ()
|
||||||
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||||
add_fmt_test(format-impl-test)
|
add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc)
|
||||||
endif ()
|
endif ()
|
||||||
add_fmt_test(locale-test)
|
|
||||||
add_fmt_test(ostream-test)
|
add_fmt_test(ostream-test)
|
||||||
add_fmt_test(compile-test)
|
add_fmt_test(compile-test)
|
||||||
add_fmt_test(printf-test)
|
add_fmt_test(printf-test)
|
||||||
add_fmt_test(custom-formatter-test)
|
|
||||||
add_fmt_test(ranges-test)
|
add_fmt_test(ranges-test)
|
||||||
add_fmt_test(scan-test)
|
add_fmt_test(scan-test)
|
||||||
|
add_fmt_test(unicode-test HEADER_ONLY)
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(unicode-test PRIVATE /utf-8)
|
||||||
|
endif ()
|
||||||
|
add_fmt_test(xchar-test)
|
||||||
|
add_fmt_test(enforce-checks-test)
|
||||||
|
target_compile_definitions(enforce-checks-test PRIVATE
|
||||||
|
-DFMT_ENFORCE_COMPILE_STRING)
|
||||||
|
|
||||||
if (NOT MSVC_BUILD_STATIC)
|
if (FMT_CAN_MODULE)
|
||||||
|
# The tests need {fmt} to be compiled as traditional library
|
||||||
|
# because of visibility of implementation details.
|
||||||
|
# If module support is present the module tests require a
|
||||||
|
# test-only module to be built from {fmt}
|
||||||
|
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
|
||||||
|
target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES})
|
||||||
|
target_include_directories(test-module PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||||
|
enable_module(test-module)
|
||||||
|
|
||||||
|
add_fmt_test(module-test MODULE)
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(test-module PRIVATE /utf-8)
|
||||||
|
target_compile_options(module-test PRIVATE /utf-8)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
|
||||||
|
foreach (flag_var
|
||||||
|
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||||
|
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||||
|
if (${flag_var} MATCHES "^(/|-)(MT|MTd)")
|
||||||
|
set(MSVC_STATIC_RUNTIME ON)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT MSVC_STATIC_RUNTIME)
|
||||||
add_fmt_executable(posix-mock-test
|
add_fmt_executable(posix-mock-test
|
||||||
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||||
target_link_libraries(posix-mock-test gmock)
|
target_link_libraries(posix-mock-test gtest)
|
||||||
target_include_directories(posix-mock-test SYSTEM PUBLIC gtest gmock)
|
|
||||||
if (FMT_PEDANTIC)
|
if (FMT_PEDANTIC)
|
||||||
target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||||
endif ()
|
endif ()
|
||||||
|
@ -126,21 +131,9 @@ if (NOT MSVC_BUILD_STATIC)
|
||||||
add_fmt_test(os-test)
|
add_fmt_test(os-test)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
add_fmt_executable(header-only-test
|
|
||||||
header-only-test.cc header-only-test2.cc test-main.cc)
|
|
||||||
target_link_libraries(header-only-test gmock)
|
|
||||||
target_include_directories(header-only-test SYSTEM PUBLIC gtest gmock)
|
|
||||||
if (TARGET fmt-header-only)
|
|
||||||
target_link_libraries(header-only-test fmt-header-only)
|
|
||||||
else ()
|
|
||||||
target_include_directories(
|
|
||||||
header-only-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
|
||||||
target_compile_definitions(header-only-test PRIVATE FMT_HEADER_ONLY=1)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
|
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
|
||||||
|
|
||||||
if (FMT_PEDANTIC)
|
if (FMT_PEDANTIC AND CXX_STANDARD LESS 20)
|
||||||
# MSVC fails to compile GMock when C++17 is enabled.
|
# MSVC fails to compile GMock when C++17 is enabled.
|
||||||
if (FMT_HAS_VARIANT AND NOT MSVC)
|
if (FMT_HAS_VARIANT AND NOT MSVC)
|
||||||
add_fmt_test(std-format-test)
|
add_fmt_test(std-format-test)
|
||||||
|
@ -195,6 +188,7 @@ if (FMT_PEDANTIC AND NOT WIN32)
|
||||||
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||||
--build-options
|
--build-options
|
||||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||||
|
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
||||||
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||||
"-DFMT_DIR=${PROJECT_BINARY_DIR}"
|
"-DFMT_DIR=${PROJECT_BINARY_DIR}"
|
||||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||||
|
@ -215,6 +209,21 @@ if (FMT_PEDANTIC AND NOT WIN32)
|
||||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
# This test are disabled on Windows because it is only *NIX issue.
|
||||||
|
if (FMT_PEDANTIC AND NOT WIN32)
|
||||||
|
add_test(static-export-test ${CMAKE_CTEST_COMMAND}
|
||||||
|
-C ${CMAKE_BUILD_TYPE}
|
||||||
|
--build-and-test
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/static-export-test"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/static-export-test"
|
||||||
|
--build-generator ${CMAKE_GENERATOR}
|
||||||
|
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
|
||||||
|
--build-options
|
||||||
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||||
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
||||||
|
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Activate optional CUDA tests if CUDA is found. For version selection see
|
# Activate optional CUDA tests if CUDA is found. For version selection see
|
||||||
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
||||||
if (FMT_CUDA_TEST)
|
if (FMT_CUDA_TEST)
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
cmake_minimum_required(VERSION 3.1.0)
|
cmake_minimum_required(VERSION 3.1...3.18)
|
||||||
|
|
||||||
project(fmt-test)
|
project(fmt-test CXX)
|
||||||
|
|
||||||
add_subdirectory(../.. fmt)
|
add_subdirectory(../.. fmt)
|
||||||
|
|
||||||
add_executable(library-test "main.cc")
|
add_executable(library-test main.cc)
|
||||||
target_link_libraries(library-test fmt::fmt)
|
|
||||||
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
|
||||||
target_include_directories(library-test PUBLIC SYSTEM .)
|
target_include_directories(library-test PUBLIC SYSTEM .)
|
||||||
|
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||||
|
target_link_libraries(library-test fmt::fmt)
|
||||||
|
|
||||||
if (TARGET fmt::fmt-header-only)
|
if (TARGET fmt::fmt-header-only)
|
||||||
add_executable(header-only-test "main.cc")
|
add_executable(header-only-test main.cc)
|
||||||
target_link_libraries(header-only-test fmt::fmt-header-only)
|
|
||||||
target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
|
||||||
target_include_directories(header-only-test PUBLIC SYSTEM .)
|
target_include_directories(header-only-test PUBLIC SYSTEM .)
|
||||||
|
target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||||
|
target_link_libraries(header-only-test fmt::fmt-header-only)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include "fmt/format.h"
|
#include "fmt/core.h"
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
for(int i = 0; i < argc; ++i)
|
for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]);
|
||||||
fmt::print("{}: {}\n", i, argv[i]);
|
|
||||||
}
|
}
|
||||||
|
|
173
test/args-test.cc
Normal file
173
test/args-test.cc
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// Formatting library for C++ - dynamic argument store tests
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include "fmt/args.h"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
TEST(args_test, basic) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
store.push_back(42);
|
||||||
|
store.push_back("abc1");
|
||||||
|
store.push_back(1.5f);
|
||||||
|
EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, strings_and_refs) {
|
||||||
|
// Unfortunately the tests are compiled with old ABI so strings use COW.
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
char str[] = "1234567890";
|
||||||
|
store.push_back(str);
|
||||||
|
store.push_back(std::cref(str));
|
||||||
|
store.push_back(fmt::string_view{str});
|
||||||
|
str[0] = 'X';
|
||||||
|
|
||||||
|
auto result = fmt::vformat("{} and {} and {}", store);
|
||||||
|
EXPECT_EQ("1234567890 and X234567890 and X234567890", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct custom_type {
|
||||||
|
int i = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<custom_type> {
|
||||||
|
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
|
return format_to(ctx.out(), "cust={}", p.i);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(args_test, custom_format) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
auto c = custom_type();
|
||||||
|
store.push_back(c);
|
||||||
|
++c.i;
|
||||||
|
store.push_back(c);
|
||||||
|
++c.i;
|
||||||
|
store.push_back(std::cref(c));
|
||||||
|
++c.i;
|
||||||
|
auto result = fmt::vformat("{} and {} and {}", store);
|
||||||
|
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct to_stringable {
|
||||||
|
friend fmt::string_view to_string_view(to_stringable) { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<to_stringable> {
|
||||||
|
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(to_stringable, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(args_test, to_string_and_formatter) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
auto s = to_stringable();
|
||||||
|
store.push_back(s);
|
||||||
|
store.push_back(std::cref(s));
|
||||||
|
fmt::vformat("", store);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, named_int) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
store.push_back(fmt::arg("a1", 42));
|
||||||
|
EXPECT_EQ("42", fmt::vformat("{a1}", store));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, named_strings) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
char str[] = "1234567890";
|
||||||
|
store.push_back(fmt::arg("a1", str));
|
||||||
|
store.push_back(fmt::arg("a2", std::cref(str)));
|
||||||
|
str[0] = 'X';
|
||||||
|
EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, named_arg_by_ref) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
char band[] = "Rolling Stones";
|
||||||
|
store.push_back(fmt::arg("band", std::cref(band)));
|
||||||
|
band[9] = 'c'; // Changing band affects the output.
|
||||||
|
EXPECT_EQ(fmt::vformat("{band}", store), "Rolling Scones");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, named_custom_format) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
auto c = custom_type();
|
||||||
|
store.push_back(fmt::arg("c1", c));
|
||||||
|
++c.i;
|
||||||
|
store.push_back(fmt::arg("c2", c));
|
||||||
|
++c.i;
|
||||||
|
store.push_back(fmt::arg("c_ref", std::cref(c)));
|
||||||
|
++c.i;
|
||||||
|
auto result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
|
||||||
|
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, clear) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
store.push_back(42);
|
||||||
|
|
||||||
|
auto result = fmt::vformat("{}", store);
|
||||||
|
EXPECT_EQ("42", result);
|
||||||
|
|
||||||
|
store.push_back(43);
|
||||||
|
result = fmt::vformat("{} and {}", store);
|
||||||
|
EXPECT_EQ("42 and 43", result);
|
||||||
|
|
||||||
|
store.clear();
|
||||||
|
store.push_back(44);
|
||||||
|
result = fmt::vformat("{}", store);
|
||||||
|
EXPECT_EQ("44", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(args_test, reserve) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
store.reserve(2, 1);
|
||||||
|
store.push_back(1.5f);
|
||||||
|
store.push_back(fmt::arg("a1", 42));
|
||||||
|
auto result = fmt::vformat("{a1} and {}", store);
|
||||||
|
EXPECT_EQ("42 and 1.5", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct copy_throwable {
|
||||||
|
copy_throwable() {}
|
||||||
|
copy_throwable(const copy_throwable&) { throw "deal with it"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<copy_throwable> {
|
||||||
|
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(args_test, throw_on_copy) {
|
||||||
|
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||||
|
store.push_back(std::string("foo"));
|
||||||
|
try {
|
||||||
|
store.push_back(copy_throwable());
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
EXPECT_EQ(fmt::vformat("{}", store), "foo");
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
// Formatting library for C++ - assertion tests
|
// Formatting library for C++ - FMT_ASSERT test
|
||||||
|
//
|
||||||
|
// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks
|
||||||
|
// which are slow on some platforms. In other tests FMT_ASSERT is made to throw
|
||||||
|
// an exception which is much faster and easier to check.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
@ -6,9 +10,9 @@
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#include "fmt/core.h"
|
#include "fmt/core.h"
|
||||||
#include "gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
TEST(AssertTest, Fail) {
|
TEST(assert_test, fail) {
|
||||||
#if GTEST_HAS_DEATH_TEST
|
#if GTEST_HAS_DEATH_TEST
|
||||||
EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||||
#else
|
#else
|
||||||
|
@ -16,9 +20,8 @@ TEST(AssertTest, Fail) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool test_condition = false;
|
TEST(assert_test, dangling_else) {
|
||||||
|
bool test_condition = false;
|
||||||
TEST(AssertTest, DanglingElse) {
|
|
||||||
bool executed_else = false;
|
bool executed_else = false;
|
||||||
if (test_condition)
|
if (test_condition)
|
||||||
FMT_ASSERT(true, "");
|
FMT_ASSERT(true, "");
|
||||||
|
|
|
@ -5,77 +5,70 @@
|
||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
# define _CRT_SECURE_NO_WARNINGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "fmt/chrono.h"
|
#include "fmt/chrono.h"
|
||||||
|
|
||||||
#include <iomanip>
|
#include "gtest-extra.h" // EXPECT_THROW_MSG
|
||||||
|
#include "util.h" // get_locale
|
||||||
|
|
||||||
#include "gtest-extra.h"
|
using fmt::runtime;
|
||||||
|
|
||||||
std::tm make_tm() {
|
using testing::Contains;
|
||||||
|
|
||||||
|
auto make_tm() -> std::tm {
|
||||||
auto time = std::tm();
|
auto time = std::tm();
|
||||||
time.tm_mday = 1;
|
time.tm_mday = 1;
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tm make_hour(int h) {
|
auto make_hour(int h) -> std::tm {
|
||||||
auto time = make_tm();
|
auto time = make_tm();
|
||||||
time.tm_hour = h;
|
time.tm_hour = h;
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tm make_minute(int m) {
|
auto make_minute(int m) -> std::tm {
|
||||||
auto time = make_tm();
|
auto time = make_tm();
|
||||||
time.tm_min = m;
|
time.tm_min = m;
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tm make_second(int s) {
|
auto make_second(int s) -> std::tm {
|
||||||
auto time = make_tm();
|
auto time = make_tm();
|
||||||
time.tm_sec = s;
|
time.tm_sec = s;
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string format_tm(const std::tm& time, const char* spec,
|
TEST(chrono_test, format_tm) {
|
||||||
const std::locale& loc) {
|
auto tm = std::tm();
|
||||||
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
|
||||||
std::ostringstream os;
|
|
||||||
os.imbue(loc);
|
|
||||||
facet.put(os, os, ' ', &time, spec, spec + std::strlen(spec));
|
|
||||||
return os.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(TimeTest, Format) {
|
|
||||||
std::tm tm = std::tm();
|
|
||||||
tm.tm_year = 116;
|
tm.tm_year = 116;
|
||||||
tm.tm_mon = 3;
|
tm.tm_mon = 3;
|
||||||
tm.tm_mday = 25;
|
tm.tm_mday = 25;
|
||||||
EXPECT_EQ("The date is 2016-04-25.",
|
tm.tm_hour = 11;
|
||||||
fmt::format("The date is {:%Y-%m-%d}.", tm));
|
tm.tm_min = 22;
|
||||||
|
tm.tm_sec = 33;
|
||||||
|
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
|
||||||
|
"The date is 2016-04-25 11:22:33.");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TimeTest, GrowBuffer) {
|
TEST(chrono_test, grow_buffer) {
|
||||||
std::string s = "{:";
|
auto s = std::string("{:");
|
||||||
for (int i = 0; i < 30; ++i) s += "%c";
|
for (int i = 0; i < 30; ++i) s += "%c";
|
||||||
s += "}\n";
|
s += "}\n";
|
||||||
std::time_t t = std::time(nullptr);
|
auto t = std::time(nullptr);
|
||||||
fmt::format(s, *std::localtime(&t));
|
fmt::format(fmt::runtime(s), *std::localtime(&t));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TimeTest, FormatToEmptyContainer) {
|
TEST(chrono_test, format_to_empty_container) {
|
||||||
std::string s;
|
|
||||||
auto time = std::tm();
|
auto time = std::tm();
|
||||||
time.tm_sec = 42;
|
time.tm_sec = 42;
|
||||||
|
auto s = std::string();
|
||||||
fmt::format_to(std::back_inserter(s), "{:%S}", time);
|
fmt::format_to(std::back_inserter(s), "{:%S}", time);
|
||||||
EXPECT_EQ(s, "42");
|
EXPECT_EQ(s, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TimeTest, EmptyResult) { EXPECT_EQ("", fmt::format("{}", std::tm())); }
|
TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
|
||||||
|
|
||||||
static bool EqualTime(const std::tm& lhs, const std::tm& rhs) {
|
auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
|
||||||
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
|
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
|
||||||
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
|
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
|
||||||
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
|
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
|
||||||
|
@ -83,28 +76,39 @@ static bool EqualTime(const std::tm& lhs, const std::tm& rhs) {
|
||||||
lhs.tm_isdst == rhs.tm_isdst;
|
lhs.tm_isdst == rhs.tm_isdst;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TimeTest, LocalTime) {
|
TEST(chrono_test, localtime) {
|
||||||
std::time_t t = std::time(nullptr);
|
auto t = std::time(nullptr);
|
||||||
std::tm tm = *std::localtime(&t);
|
auto tm = *std::localtime(&t);
|
||||||
EXPECT_TRUE(EqualTime(tm, fmt::localtime(t)));
|
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TimeTest, GMTime) {
|
TEST(chrono_test, gmtime) {
|
||||||
std::time_t t = std::time(nullptr);
|
auto t = std::time(nullptr);
|
||||||
std::tm tm = *std::gmtime(&t);
|
auto tm = *std::gmtime(&t);
|
||||||
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t)));
|
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define EXPECT_TIME(spec, time, duration) \
|
template <typename TimePoint> auto strftime(TimePoint tp) -> std::string {
|
||||||
{ \
|
auto t = std::chrono::system_clock::to_time_t(tp);
|
||||||
std::locale loc("ja_JP.utf8"); \
|
auto tm = *std::localtime(&t);
|
||||||
EXPECT_EQ(format_tm(time, spec, loc), \
|
char output[256] = {};
|
||||||
fmt::format(loc, "{:" spec "}", duration)); \
|
std::strftime(output, sizeof(output), "%Y-%m-%d %H:%M:%S", &tm);
|
||||||
}
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, time_point) {
|
||||||
|
auto t1 = std::chrono::system_clock::now();
|
||||||
|
EXPECT_EQ(strftime(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||||
|
EXPECT_EQ(strftime(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(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|
||||||
TEST(ChronoTest, FormatDefault) {
|
TEST(chrono_test, format_default) {
|
||||||
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
|
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
|
||||||
EXPECT_EQ("42as",
|
EXPECT_EQ("42as",
|
||||||
fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
|
fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
|
||||||
|
@ -146,49 +150,7 @@ TEST(ChronoTest, FormatDefault) {
|
||||||
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatWide) {
|
TEST(chrono_test, align) {
|
||||||
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
|
||||||
EXPECT_EQ(L"42as",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::atto>(42)));
|
|
||||||
EXPECT_EQ(L"42fs",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::femto>(42)));
|
|
||||||
EXPECT_EQ(L"42ps",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::pico>(42)));
|
|
||||||
EXPECT_EQ(L"42ns", fmt::format(L"{}", std::chrono::nanoseconds(42)));
|
|
||||||
EXPECT_EQ(L"42\u00B5s", fmt::format(L"{}", std::chrono::microseconds(42)));
|
|
||||||
EXPECT_EQ(L"42ms", fmt::format(L"{}", std::chrono::milliseconds(42)));
|
|
||||||
EXPECT_EQ(L"42cs",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::centi>(42)));
|
|
||||||
EXPECT_EQ(L"42ds",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::deci>(42)));
|
|
||||||
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
|
||||||
EXPECT_EQ(L"42das",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::deca>(42)));
|
|
||||||
EXPECT_EQ(L"42hs",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::hecto>(42)));
|
|
||||||
EXPECT_EQ(L"42ks",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::kilo>(42)));
|
|
||||||
EXPECT_EQ(L"42Ms",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::mega>(42)));
|
|
||||||
EXPECT_EQ(L"42Gs",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::giga>(42)));
|
|
||||||
EXPECT_EQ(L"42Ts",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::tera>(42)));
|
|
||||||
EXPECT_EQ(L"42Ps",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::peta>(42)));
|
|
||||||
EXPECT_EQ(L"42Es",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::exa>(42)));
|
|
||||||
EXPECT_EQ(L"42m", fmt::format(L"{}", std::chrono::minutes(42)));
|
|
||||||
EXPECT_EQ(L"42h", fmt::format(L"{}", std::chrono::hours(42)));
|
|
||||||
EXPECT_EQ(
|
|
||||||
L"42[15]s",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
|
|
||||||
EXPECT_EQ(
|
|
||||||
L"42[15/4]s",
|
|
||||||
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ChronoTest, 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));
|
||||||
|
@ -204,7 +166,7 @@ TEST(ChronoTest, Align) {
|
||||||
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatSpecs) {
|
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)));
|
||||||
EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0)));
|
EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0)));
|
||||||
|
@ -233,43 +195,64 @@ TEST(ChronoTest, FormatSpecs) {
|
||||||
EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
|
EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, InvalidSpecs) {
|
TEST(chrono_test, invalid_specs) {
|
||||||
auto sec = std::chrono::seconds(0);
|
auto sec = std::chrono::seconds(0);
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%a}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%a}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%A}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%c}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%A}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%x}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%Ex}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%c}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%X}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%EX}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%x}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%D}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%F}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%Ex}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%Ec}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%w}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%X}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%u}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%b}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%EX}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%B}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%z}", sec), fmt::format_error, "no date");
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%D}"), sec), fmt::format_error,
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%Z}", sec), fmt::format_error, "no date");
|
"no date");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%Eq}", sec), fmt::format_error,
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%F}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%Ec}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%w}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%u}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%b}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%B}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%z}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%Z}"), sec), fmt::format_error,
|
||||||
|
"no date");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%Eq}"), sec), fmt::format_error,
|
||||||
"invalid format");
|
"invalid format");
|
||||||
EXPECT_THROW_MSG(fmt::format("{:%Oq}", sec), fmt::format_error,
|
EXPECT_THROW_MSG(fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
|
||||||
"invalid format");
|
"invalid format");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, Locale) {
|
auto format_tm(const std::tm& time, fmt::string_view spec,
|
||||||
const char* loc_name = "ja_JP.utf8";
|
const std::locale& loc) -> std::string {
|
||||||
bool has_locale = false;
|
auto& facet = std::use_facet<std::time_put<char>>(loc);
|
||||||
std::locale loc;
|
std::ostringstream os;
|
||||||
try {
|
os.imbue(loc);
|
||||||
loc = std::locale(loc_name);
|
facet.put(os, os, ' ', &time, spec.begin(), spec.end());
|
||||||
has_locale = true;
|
return os.str();
|
||||||
} catch (const std::runtime_error&) {
|
}
|
||||||
}
|
|
||||||
if (!has_locale) {
|
TEST(chrono_test, locale) {
|
||||||
fmt::print("{} locale is missing.\n", loc_name);
|
auto loc = get_locale("ja_JP.utf8");
|
||||||
return;
|
if (loc == std::locale::classic()) return;
|
||||||
}
|
# define EXPECT_TIME(spec, time, duration) \
|
||||||
|
{ \
|
||||||
|
auto jp_loc = std::locale("ja_JP.utf8"); \
|
||||||
|
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
||||||
|
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
||||||
|
}
|
||||||
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
||||||
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
||||||
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
||||||
|
@ -283,9 +266,9 @@ TEST(ChronoTest, Locale) {
|
||||||
EXPECT_TIME("%p", time, sec);
|
EXPECT_TIME("%p", time, sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef std::chrono::duration<double, std::milli> dms;
|
using dms = std::chrono::duration<double, std::milli>;
|
||||||
|
|
||||||
TEST(ChronoTest, FormatDefaultFP) {
|
TEST(chrono_test, format_default_fp) {
|
||||||
typedef std::chrono::duration<float> fs;
|
typedef std::chrono::duration<float> fs;
|
||||||
EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
|
EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
|
||||||
typedef std::chrono::duration<float, std::milli> fms;
|
typedef std::chrono::duration<float, std::milli> fms;
|
||||||
|
@ -295,15 +278,15 @@ TEST(ChronoTest, FormatDefaultFP) {
|
||||||
EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
|
EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatPrecision) {
|
TEST(chrono_test, format_precision) {
|
||||||
EXPECT_THROW_MSG(fmt::format("{:.2}", std::chrono::seconds(42)),
|
EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
|
||||||
fmt::format_error,
|
fmt::format_error,
|
||||||
"precision not allowed for this argument type");
|
"precision not allowed for this argument type");
|
||||||
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
|
||||||
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
|
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatFullSpecs) {
|
TEST(chrono_test, format_full_specs) {
|
||||||
EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
|
EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
|
||||||
EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
|
EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
|
||||||
EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
|
EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
|
||||||
|
@ -312,7 +295,7 @@ TEST(ChronoTest, FormatFullSpecs) {
|
||||||
EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
|
EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatSimpleQq) {
|
TEST(chrono_test, format_simple_q) {
|
||||||
typedef std::chrono::duration<float> fs;
|
typedef std::chrono::duration<float> fs;
|
||||||
EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
|
EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
|
||||||
typedef std::chrono::duration<float, std::milli> fms;
|
typedef std::chrono::duration<float, std::milli> fms;
|
||||||
|
@ -322,15 +305,15 @@ TEST(ChronoTest, FormatSimpleQq) {
|
||||||
EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234)));
|
EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatPrecisionQq) {
|
TEST(chrono_test, format_precision_q) {
|
||||||
EXPECT_THROW_MSG(fmt::format("{:.2%Q %q}", std::chrono::seconds(42)),
|
EXPECT_THROW_MSG(fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)),
|
||||||
fmt::format_error,
|
fmt::format_error,
|
||||||
"precision not allowed for this argument type");
|
"precision not allowed for this argument type");
|
||||||
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
|
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
|
||||||
EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
|
EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, FormatFullSpecsQq) {
|
TEST(chrono_test, format_full_specs_q) {
|
||||||
EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
|
EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
|
||||||
EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
|
EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
|
||||||
EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
|
EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
|
||||||
|
@ -339,17 +322,17 @@ TEST(ChronoTest, FormatFullSpecsQq) {
|
||||||
EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
|
EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, InvalidWidthId) {
|
TEST(chrono_test, invalid_width_id) {
|
||||||
EXPECT_THROW(fmt::format("{:{o}", std::chrono::seconds(0)),
|
EXPECT_THROW(fmt::format(runtime("{:{o}"), std::chrono::seconds(0)),
|
||||||
fmt::format_error);
|
fmt::format_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, InvalidColons) {
|
TEST(chrono_test, invalid_colons) {
|
||||||
EXPECT_THROW(fmt::format("{0}=:{0::", std::chrono::seconds(0)),
|
EXPECT_THROW(fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)),
|
||||||
fmt::format_error);
|
fmt::format_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, NegativeDurations) {
|
TEST(chrono_test, negative_durations) {
|
||||||
EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
|
EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
|
||||||
EXPECT_EQ("-03:25:45",
|
EXPECT_EQ("-03:25:45",
|
||||||
fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
|
fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
|
||||||
|
@ -364,7 +347,7 @@ TEST(ChronoTest, NegativeDurations) {
|
||||||
fmt::format("{:%Q}", std::chrono::duration<int>(min)));
|
fmt::format("{:%Q}", std::chrono::duration<int>(min)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChronoTest, SpecialDurations) {
|
TEST(chrono_test, special_durations) {
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
"40.",
|
"40.",
|
||||||
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
|
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
|
||||||
|
@ -384,4 +367,19 @@ TEST(ChronoTest, SpecialDurations) {
|
||||||
"03:33:20");
|
"03:33:20");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, unsigned_duration) {
|
||||||
|
EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(chrono_test, weekday) {
|
||||||
|
auto loc = get_locale("ru_RU.UTF-8");
|
||||||
|
std::locale::global(loc);
|
||||||
|
auto mon = fmt::weekday(1);
|
||||||
|
EXPECT_EQ(fmt::format("{}", mon), "Mon");
|
||||||
|
if (loc != std::locale::classic()) {
|
||||||
|
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
|
||||||
|
Contains(fmt::format(loc, "{:L}", mon)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
|
|
@ -7,52 +7,13 @@
|
||||||
|
|
||||||
#include "fmt/color.h"
|
#include "fmt/color.h"
|
||||||
|
|
||||||
#include "gtest-extra.h"
|
#include <iterator> // std::back_inserter
|
||||||
|
|
||||||
TEST(ColorsTest, ColorsPrint) {
|
#include "gtest-extra.h" // EXPECT_WRITE
|
||||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
|
||||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::color::blue), "blue"),
|
|
||||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
|
||||||
EXPECT_WRITE(
|
|
||||||
stdout,
|
|
||||||
fmt::print(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
|
|
||||||
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::bold, "bold"),
|
|
||||||
"\x1b[1mbold\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::italic, "italic"),
|
|
||||||
"\x1b[3mitalic\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::underline, "underline"),
|
|
||||||
"\x1b[4munderline\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout,
|
|
||||||
fmt::print(fmt::emphasis::strikethrough, "strikethrough"),
|
|
||||||
"\x1b[9mstrikethrough\x1b[0m");
|
|
||||||
EXPECT_WRITE(
|
|
||||||
stdout,
|
|
||||||
fmt::print(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
|
|
||||||
"\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
|
|
||||||
EXPECT_WRITE(stderr, fmt::print(stderr, fmt::emphasis::bold, "bold error"),
|
|
||||||
"\x1b[1mbold error\x1b[0m");
|
|
||||||
EXPECT_WRITE(stderr, fmt::print(stderr, fg(fmt::color::blue), "blue log"),
|
|
||||||
"\x1b[38;2;000;000;255mblue log\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(fmt::text_style(), "hi"), "hi");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(fg(fmt::terminal_color::red), "tred"),
|
|
||||||
"\x1b[31mtred\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout, fmt::print(bg(fmt::terminal_color::cyan), "tcyan"),
|
|
||||||
"\x1b[46mtcyan\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout,
|
|
||||||
fmt::print(fg(fmt::terminal_color::bright_green), "tbgreen"),
|
|
||||||
"\x1b[92mtbgreen\x1b[0m");
|
|
||||||
EXPECT_WRITE(stdout,
|
|
||||||
fmt::print(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
|
|
||||||
"\x1b[105mtbmagenta\x1b[0m");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ColorsTest, Format) {
|
TEST(color_test, format) {
|
||||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
|
|
||||||
L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
|
|
||||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
||||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
|
@ -84,3 +45,16 @@ TEST(ColorsTest, Format) {
|
||||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
|
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
|
||||||
"\x1b[31mfoo\x1b[0m");
|
"\x1b[31mfoo\x1b[0m");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(color_test, format_to) {
|
||||||
|
auto out = std::string();
|
||||||
|
fmt::format_to(std::back_inserter(out), fg(fmt::rgb(255, 20, 30)),
|
||||||
|
"rgb(255,20,30){}{}{}", 1, 2, 3);
|
||||||
|
EXPECT_EQ(fmt::to_string(out),
|
||||||
|
"\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(color_test, print) {
|
||||||
|
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||||
|
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||||
|
}
|
||||||
|
|
|
@ -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.0)
|
cmake_minimum_required(VERSION 3.1...3.18)
|
||||||
|
|
||||||
include(CheckCXXSourceCompiles)
|
include(CheckCXXSourceCompiles)
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
|
|
|
@ -5,138 +5,83 @@
|
||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <cfloat>
|
|
||||||
#include <climits>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
#include <deque>
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Check if fmt/compile.h compiles with windows.h included before it.
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "fmt/compile.h"
|
#include "fmt/compile.h"
|
||||||
#include "gmock.h"
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "fmt/chrono.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "mock-allocator.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#undef ERROR
|
TEST(iterator_test, counting_iterator) {
|
||||||
#undef min
|
auto it = fmt::detail::counting_iterator();
|
||||||
#undef max
|
auto prev = it++;
|
||||||
|
EXPECT_EQ(prev.count(), 0);
|
||||||
using testing::Return;
|
EXPECT_EQ(it.count(), 1);
|
||||||
using testing::StrictMock;
|
EXPECT_EQ((it + 41).count(), 42);
|
||||||
|
|
||||||
// compiletime_prepared_parts_type_provider is useful only with relaxed
|
|
||||||
// constexpr.
|
|
||||||
#if FMT_USE_CONSTEXPR
|
|
||||||
template <unsigned EXPECTED_PARTS_COUNT, typename Format>
|
|
||||||
void check_prepared_parts_type(Format format) {
|
|
||||||
typedef fmt::detail::compiled_format_base<decltype(format)> provider;
|
|
||||||
typedef fmt::detail::format_part<char>
|
|
||||||
expected_parts_type[EXPECTED_PARTS_COUNT];
|
|
||||||
static_assert(std::is_same<typename provider::parts_container,
|
|
||||||
expected_parts_type>::value,
|
|
||||||
"CompileTimePreparedPartsTypeProvider test failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
|
TEST(iterator_test, truncating_iterator) {
|
||||||
check_prepared_parts_type<1u>(FMT_STRING("text"));
|
char* p = nullptr;
|
||||||
check_prepared_parts_type<1u>(FMT_STRING("{}"));
|
auto it = fmt::detail::truncating_iterator<char*>(p, 3);
|
||||||
check_prepared_parts_type<2u>(FMT_STRING("text{}"));
|
auto prev = it++;
|
||||||
check_prepared_parts_type<2u>(FMT_STRING("{}text"));
|
EXPECT_EQ(prev.base(), p);
|
||||||
check_prepared_parts_type<3u>(FMT_STRING("text{}text"));
|
EXPECT_EQ(it.base(), p + 1);
|
||||||
check_prepared_parts_type<3u>(FMT_STRING("{:{}.{}} {:{}}"));
|
}
|
||||||
|
|
||||||
check_prepared_parts_type<3u>(FMT_STRING("{{{}}}")); // '{', 'argument', '}'
|
TEST(iterator_test, truncating_iterator_default_construct) {
|
||||||
check_prepared_parts_type<2u>(FMT_STRING("text{{")); // 'text', '{'
|
auto it = fmt::detail::truncating_iterator<char*>();
|
||||||
check_prepared_parts_type<3u>(FMT_STRING("text{{ ")); // 'text', '{', ' '
|
EXPECT_EQ(nullptr, it.base());
|
||||||
check_prepared_parts_type<2u>(FMT_STRING("}}text")); // '}', text
|
EXPECT_EQ(std::size_t{0}, it.count());
|
||||||
check_prepared_parts_type<2u>(FMT_STRING("text}}text")); // 'text}', 'text'
|
}
|
||||||
check_prepared_parts_type<4u>(
|
|
||||||
FMT_STRING("text{{}}text")); // 'text', '{', '}', 'text'
|
#ifdef __cpp_lib_ranges
|
||||||
|
TEST(iterator_test, truncating_iterator_is_output_iterator) {
|
||||||
|
static_assert(
|
||||||
|
std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(CompileTest, PassStringLiteralFormat) {
|
TEST(iterator_test, truncating_back_inserter) {
|
||||||
const auto prepared = fmt::detail::compile<int>("test {}");
|
auto buffer = std::string();
|
||||||
EXPECT_EQ("test 42", fmt::format(prepared, 42));
|
auto bi = std::back_inserter(buffer);
|
||||||
const auto wprepared = fmt::detail::compile<int>(L"test {}");
|
auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
|
||||||
EXPECT_EQ(L"test 42", fmt::format(wprepared, 42));
|
*it++ = '4';
|
||||||
|
*it++ = '2';
|
||||||
|
*it++ = '1';
|
||||||
|
EXPECT_EQ(buffer.size(), 2);
|
||||||
|
EXPECT_EQ(buffer, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, FormatToArrayOfChars) {
|
TEST(compile_test, compile_fallback) {
|
||||||
char buffer[32] = {0};
|
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
|
||||||
const auto prepared = fmt::detail::compile<int>("4{}");
|
// not available.
|
||||||
fmt::format_to(fmt::detail::make_checked(buffer, 32), prepared, 2);
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
|
||||||
EXPECT_EQ(std::string("42"), buffer);
|
|
||||||
wchar_t wbuffer[32] = {0};
|
|
||||||
const auto wprepared = fmt::detail::compile<int>(L"4{}");
|
|
||||||
fmt::format_to(fmt::detail::make_checked(wbuffer, 32), wprepared, 2);
|
|
||||||
EXPECT_EQ(std::wstring(L"42"), wbuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, FormatToIterator) {
|
#ifdef __cpp_if_constexpr
|
||||||
std::string s(2, ' ');
|
struct test_formattable {};
|
||||||
const auto prepared = fmt::detail::compile<int>("4{}");
|
|
||||||
fmt::format_to(s.begin(), prepared, 2);
|
|
||||||
EXPECT_EQ("42", s);
|
|
||||||
std::wstring ws(2, L' ');
|
|
||||||
const auto wprepared = fmt::detail::compile<int>(L"4{}");
|
|
||||||
fmt::format_to(ws.begin(), wprepared, 2);
|
|
||||||
EXPECT_EQ(L"42", ws);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(CompileTest, FormatToN) {
|
|
||||||
char buf[5];
|
|
||||||
auto f = fmt::detail::compile<int>("{:10}");
|
|
||||||
auto result = fmt::format_to_n(buf, 5, f, 42);
|
|
||||||
EXPECT_EQ(result.size, 10);
|
|
||||||
EXPECT_EQ(result.out, buf + 5);
|
|
||||||
EXPECT_EQ(fmt::string_view(buf, 5), " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(CompileTest, FormattedSize) {
|
|
||||||
auto f = fmt::detail::compile<int>("{:10}");
|
|
||||||
EXPECT_EQ(fmt::formatted_size(f, 42), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(CompileTest, MultipleTypes) {
|
|
||||||
auto f = fmt::detail::compile<int, int>("{} {}");
|
|
||||||
EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct formattable {};
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template <> struct formatter<formattable> : formatter<const char*> {
|
template <> struct formatter<test_formattable> : formatter<const char*> {
|
||||||
|
char word_spec = 'f';
|
||||||
|
constexpr auto parse(format_parse_context& ctx) {
|
||||||
|
auto it = ctx.begin(), end = ctx.end();
|
||||||
|
if (it == end || *it == '}') return it;
|
||||||
|
if (it != end && (*it == 'f' || *it == 'b')) word_spec = *it++;
|
||||||
|
if (it != end && *it != '}') throw format_error("invalid format");
|
||||||
|
return it;
|
||||||
|
}
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(formattable, FormatContext& ctx) -> decltype(ctx.out()) {
|
constexpr auto format(test_formattable, FormatContext& ctx) const
|
||||||
return formatter<const char*>::format("foo", ctx);
|
-> decltype(ctx.out()) {
|
||||||
|
return formatter<const char*>::format(word_spec == 'f' ? "foo" : "bar",
|
||||||
|
ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
TEST(CompileTest, FormatUserDefinedType) {
|
TEST(compile_test, format_default) {
|
||||||
auto f = fmt::detail::compile<formattable>("{}");
|
|
||||||
EXPECT_EQ(fmt::format(f, formattable()), "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(CompileTest, EmptyFormatString) {
|
|
||||||
auto f = fmt::detail::compile<>("");
|
|
||||||
EXPECT_EQ(fmt::format(f), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cpp_if_constexpr
|
|
||||||
TEST(CompileTest, FormatDefault) {
|
|
||||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
|
||||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
|
||||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
|
||||||
|
@ -146,21 +91,269 @@ TEST(CompileTest, FormatDefault) {
|
||||||
EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2));
|
EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2));
|
||||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
|
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
|
||||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
|
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
|
||||||
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), formattable()));
|
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable()));
|
||||||
|
auto t = std::chrono::system_clock::now();
|
||||||
|
EXPECT_EQ(fmt::format("{}", t), fmt::format(FMT_COMPILE("{}"), t));
|
||||||
|
# ifdef __cpp_lib_byte
|
||||||
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), std::byte{42}));
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, FormatSpecs) {
|
TEST(compile_test, format_wide_string) {
|
||||||
|
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, format_specs) {
|
||||||
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
|
||||||
|
EXPECT_EQ("1.2 ms ",
|
||||||
|
fmt::format(FMT_COMPILE("{:7.1%Q %q}"),
|
||||||
|
std::chrono::duration<double, std::milli>(1.234)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, FormatTo) {
|
TEST(compile_test, dynamic_format_specs) {
|
||||||
|
EXPECT_EQ("foo ", fmt::format(FMT_COMPILE("{:{}}"), "foo", 5));
|
||||||
|
EXPECT_EQ(" 3.14", fmt::format(FMT_COMPILE("{:{}.{}f}"), 3.141592, 6, 2));
|
||||||
|
EXPECT_EQ(
|
||||||
|
"=1.234ms=",
|
||||||
|
fmt::format(FMT_COMPILE("{:=^{}.{}}"),
|
||||||
|
std::chrono::duration<double, std::milli>(1.234), 9, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, manual_ordering) {
|
||||||
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42));
|
||||||
|
EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42));
|
||||||
|
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43));
|
||||||
|
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{1} {0}"), 43, 41));
|
||||||
|
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {2}"), 41, 42, 43));
|
||||||
|
EXPECT_EQ(" 41 43", fmt::format(FMT_COMPILE("{1:{2}} {0:4}"), 43, 41, 4));
|
||||||
|
EXPECT_EQ("42 1.2 ms ",
|
||||||
|
fmt::format(FMT_COMPILE("{0} {1:7.1%Q %q}"), 42,
|
||||||
|
std::chrono::duration<double, std::milli>(1.234)));
|
||||||
|
EXPECT_EQ(
|
||||||
|
"true 42 42 foo 0x1234 foo",
|
||||||
|
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
|
||||||
|
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
|
||||||
|
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, named) {
|
||||||
|
auto runtime_named_field_compiled =
|
||||||
|
fmt::detail::compile<decltype(fmt::arg("arg", 42))>(FMT_COMPILE("{arg}"));
|
||||||
|
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
|
||||||
|
fmt::detail::runtime_named_field<char>>);
|
||||||
|
|
||||||
|
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
|
||||||
|
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
|
||||||
|
fmt::arg("arg", 43)));
|
||||||
|
|
||||||
|
EXPECT_EQ("foobar",
|
||||||
|
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
|
||||||
|
fmt::arg("a1", "bar")));
|
||||||
|
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{}{a1}"), fmt::arg("a0", "foo"),
|
||||||
|
fmt::arg("a1", "bar")));
|
||||||
|
EXPECT_EQ("foofoo", fmt::format(FMT_COMPILE("{a0}{}"), fmt::arg("a0", "foo"),
|
||||||
|
fmt::arg("a1", "bar")));
|
||||||
|
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{0}{a1}"), fmt::arg("a0", "foo"),
|
||||||
|
fmt::arg("a1", "bar")));
|
||||||
|
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{a0}{1}"), fmt::arg("a0", "foo"),
|
||||||
|
fmt::arg("a1", "bar")));
|
||||||
|
|
||||||
|
EXPECT_EQ("foobar",
|
||||||
|
fmt::format(FMT_COMPILE("{}{a1}"), "foo", fmt::arg("a1", "bar")));
|
||||||
|
EXPECT_EQ("foobar",
|
||||||
|
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a1", "bar"),
|
||||||
|
fmt::arg("a2", "baz"), fmt::arg("a0", "foo")));
|
||||||
|
EXPECT_EQ(" bar foo ",
|
||||||
|
fmt::format(FMT_COMPILE(" {foo} {bar} "), fmt::arg("foo", "bar"),
|
||||||
|
fmt::arg("bar", "foo")));
|
||||||
|
|
||||||
|
EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)),
|
||||||
|
fmt::format_error);
|
||||||
|
|
||||||
|
# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
using namespace fmt::literals;
|
||||||
|
auto statically_named_field_compiled =
|
||||||
|
fmt::detail::compile<decltype("arg"_a = 42)>(FMT_COMPILE("{arg}"));
|
||||||
|
static_assert(std::is_same_v<decltype(statically_named_field_compiled),
|
||||||
|
fmt::detail::field<char, int, 0>>);
|
||||||
|
|
||||||
|
EXPECT_EQ("41 43",
|
||||||
|
fmt::format(FMT_COMPILE("{a0} {a1}"), "a0"_a = 41, "a1"_a = 43));
|
||||||
|
EXPECT_EQ("41 43",
|
||||||
|
fmt::format(FMT_COMPILE("{a1} {a0}"), "a0"_a = 43, "a1"_a = 41));
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, format_to) {
|
||||||
char buf[8];
|
char buf[8];
|
||||||
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
|
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
|
||||||
*end = '\0';
|
*end = '\0';
|
||||||
EXPECT_STREQ("42", buf);
|
EXPECT_STREQ("42", buf);
|
||||||
|
end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ("2a", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, TextAndArg) {
|
TEST(compile_test, format_to_n) {
|
||||||
|
constexpr auto buffer_size = 8;
|
||||||
|
char buffer[buffer_size];
|
||||||
|
auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42);
|
||||||
|
*res.out = '\0';
|
||||||
|
EXPECT_STREQ("42", buffer);
|
||||||
|
res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42);
|
||||||
|
*res.out = '\0';
|
||||||
|
EXPECT_STREQ("2a", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, formatted_size) {
|
||||||
|
EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42));
|
||||||
|
EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, text_and_arg) {
|
||||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||||
|
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, unknown_format_fallback) {
|
||||||
|
EXPECT_EQ(" 42 ",
|
||||||
|
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
|
||||||
|
|
||||||
|
std::vector<char> v;
|
||||||
|
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"),
|
||||||
|
fmt::arg("name", 42));
|
||||||
|
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size()));
|
||||||
|
|
||||||
|
char buffer[4];
|
||||||
|
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
|
||||||
|
fmt::arg("name", 42));
|
||||||
|
EXPECT_EQ(5u, result.size);
|
||||||
|
EXPECT_EQ(buffer + 4, result.out);
|
||||||
|
EXPECT_EQ(" 42 ", fmt::string_view(buffer, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); }
|
||||||
|
|
||||||
|
struct to_stringable {
|
||||||
|
friend fmt::string_view to_string_view(to_stringable) { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <> struct formatter<to_stringable> {
|
||||||
|
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const to_stringable&, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
TEST(compile_test, to_string_and_formatter) {
|
||||||
|
fmt::format(FMT_COMPILE("{}"), to_stringable());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_test, print) {
|
||||||
|
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
|
||||||
|
"Don't panic!");
|
||||||
|
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
|
||||||
|
"Don't panic!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
TEST(compile_test, compile_format_string_literal) {
|
||||||
|
using namespace fmt::literals;
|
||||||
|
EXPECT_EQ("", fmt::format(""_cf));
|
||||||
|
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
|
||||||
|
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus >= 202002L || \
|
||||||
|
(__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
|
||||||
|
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||||
|
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||||
|
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
||||||
|
}
|
||||||
|
Char buffer[max_string_length]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t max_string_length, typename Char = char, typename... Args>
|
||||||
|
consteval auto test_format(auto format, const Args&... args) {
|
||||||
|
test_string<max_string_length, Char> string{};
|
||||||
|
fmt::format_to(string.buffer, format, args...);
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, bool) {
|
||||||
|
EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true));
|
||||||
|
EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false));
|
||||||
|
EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true));
|
||||||
|
EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, integer) {
|
||||||
|
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), 42));
|
||||||
|
EXPECT_EQ("420", test_format<4>(FMT_COMPILE("{}"), 420));
|
||||||
|
EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42));
|
||||||
|
EXPECT_EQ("42 42",
|
||||||
|
test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42}));
|
||||||
|
|
||||||
|
EXPECT_EQ("+42", test_format<4>(FMT_COMPILE("{:+}"), 42));
|
||||||
|
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{:-}"), 42));
|
||||||
|
EXPECT_EQ(" 42", test_format<4>(FMT_COMPILE("{: }"), 42));
|
||||||
|
|
||||||
|
EXPECT_EQ("-0042", test_format<6>(FMT_COMPILE("{:05}"), -42));
|
||||||
|
|
||||||
|
EXPECT_EQ("101010", test_format<7>(FMT_COMPILE("{:b}"), 42));
|
||||||
|
EXPECT_EQ("0b101010", test_format<9>(FMT_COMPILE("{:#b}"), 42));
|
||||||
|
EXPECT_EQ("0B101010", test_format<9>(FMT_COMPILE("{:#B}"), 42));
|
||||||
|
EXPECT_EQ("042", test_format<4>(FMT_COMPILE("{:#o}"), 042));
|
||||||
|
EXPECT_EQ("0x4a", test_format<5>(FMT_COMPILE("{:#x}"), 0x4a));
|
||||||
|
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
|
||||||
|
|
||||||
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
|
||||||
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
|
||||||
|
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
|
||||||
|
|
||||||
|
EXPECT_EQ("42 ", test_format<5>(FMT_COMPILE("{:<4}"), 42));
|
||||||
|
EXPECT_EQ(" 42", test_format<5>(FMT_COMPILE("{:>4}"), 42));
|
||||||
|
EXPECT_EQ(" 42 ", test_format<5>(FMT_COMPILE("{:^4}"), 42));
|
||||||
|
EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, char) {
|
||||||
|
EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c'));
|
||||||
|
|
||||||
|
EXPECT_EQ("c ", test_format<4>(FMT_COMPILE("{:3}"), 'c'));
|
||||||
|
EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c'));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, string) {
|
||||||
|
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42"));
|
||||||
|
EXPECT_EQ("The answer is 42",
|
||||||
|
test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42"));
|
||||||
|
|
||||||
|
EXPECT_EQ("abc**", test_format<6>(FMT_COMPILE("{:*<5}"), "abc"));
|
||||||
|
EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^6}"), "🤡"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, combination) {
|
||||||
|
EXPECT_EQ("420, true, answer",
|
||||||
|
test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer"));
|
||||||
|
|
||||||
|
EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, custom_type) {
|
||||||
|
EXPECT_EQ("foo", test_format<4>(FMT_COMPILE("{}"), test_formattable()));
|
||||||
|
EXPECT_EQ("bar", test_format<4>(FMT_COMPILE("{:b}"), test_formattable()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(compile_time_formatting_test, multibyte_fill) {
|
||||||
|
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,58 +0,0 @@
|
||||||
// Formatting library for C++ - custom argument formatter tests
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// For the license information refer to format.h.
|
|
||||||
|
|
||||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
|
||||||
#define _CRT_SECURE_NO_WARNINGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "fmt/format.h"
|
|
||||||
#include "gtest-extra.h"
|
|
||||||
|
|
||||||
// MSVC 2013 is known to be broken.
|
|
||||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
|
||||||
|
|
||||||
// A custom argument formatter that doesn't print `-` for floating-point values
|
|
||||||
// rounded to 0.
|
|
||||||
class custom_arg_formatter
|
|
||||||
: public fmt::detail::arg_formatter<fmt::format_context::iterator, char> {
|
|
||||||
public:
|
|
||||||
using base = fmt::detail::arg_formatter<fmt::format_context::iterator, char>;
|
|
||||||
|
|
||||||
custom_arg_formatter(fmt::format_context& ctx,
|
|
||||||
fmt::format_parse_context* parse_ctx,
|
|
||||||
fmt::format_specs* s = nullptr,
|
|
||||||
const char* = nullptr)
|
|
||||||
: base(ctx, parse_ctx, s) {}
|
|
||||||
|
|
||||||
using base::operator();
|
|
||||||
|
|
||||||
iterator operator()(double value) {
|
|
||||||
// Comparing a float to 0.0 is safe.
|
|
||||||
if (round(value * pow(10, specs()->precision)) == 0.0) value = 0;
|
|
||||||
return base::operator()(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string custom_vformat(fmt::string_view format_str, fmt::format_args args) {
|
|
||||||
fmt::memory_buffer buffer;
|
|
||||||
fmt::detail::buffer<char>& base = buffer;
|
|
||||||
// Pass custom argument formatter as a template arg to vwrite.
|
|
||||||
fmt::vformat_to<custom_arg_formatter>(std::back_inserter(base), format_str,
|
|
||||||
args);
|
|
||||||
return std::string(buffer.data(), buffer.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
std::string custom_format(const char* format_str, const Args&... args) {
|
|
||||||
auto va = fmt::make_format_args(args...);
|
|
||||||
return custom_vformat(format_str, va);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(CustomFormatterTest, Format) {
|
|
||||||
EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001));
|
|
||||||
}
|
|
||||||
#endif
|
|
62
test/enforce-checks-test.cc
Normal file
62
test/enforce-checks-test.cc
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Formatting library for C++ - formatting library tests
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fmt/chrono.h"
|
||||||
|
#include "fmt/color.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
#include "fmt/ostream.h"
|
||||||
|
#include "fmt/ranges.h"
|
||||||
|
#include "fmt/xchar.h"
|
||||||
|
|
||||||
|
// Exercise the API to verify that everything we expect to can compile.
|
||||||
|
void test_format_api() {
|
||||||
|
fmt::format(FMT_STRING("{}"), 42);
|
||||||
|
fmt::format(FMT_STRING(L"{}"), 42);
|
||||||
|
fmt::format(FMT_STRING("noop"));
|
||||||
|
|
||||||
|
fmt::to_string(42);
|
||||||
|
fmt::to_wstring(42);
|
||||||
|
|
||||||
|
std::vector<char> out;
|
||||||
|
fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42);
|
||||||
|
|
||||||
|
char buffer[4];
|
||||||
|
fmt::format_to_n(buffer, 3, FMT_STRING("{}"), 12345);
|
||||||
|
|
||||||
|
wchar_t wbuffer[4];
|
||||||
|
fmt::format_to_n(wbuffer, 3, FMT_STRING(L"{}"), 12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_chrono() {
|
||||||
|
fmt::format(FMT_STRING("{}"), std::chrono::seconds(42));
|
||||||
|
fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_text_style() {
|
||||||
|
fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
|
||||||
|
fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
|
||||||
|
|
||||||
|
fmt::text_style ts = fg(fmt::rgb(255, 20, 30));
|
||||||
|
std::string out;
|
||||||
|
fmt::format_to(std::back_inserter(out), ts,
|
||||||
|
FMT_STRING("rgb(255,20,30){}{}{}"), 1, 2, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_range() {
|
||||||
|
std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'};
|
||||||
|
fmt::format(FMT_STRING("{}"), hello);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
test_format_api();
|
||||||
|
test_chrono();
|
||||||
|
test_text_style();
|
||||||
|
test_range();
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.1.0)
|
cmake_minimum_required(VERSION 3.1...3.18)
|
||||||
|
|
||||||
project(fmt-test)
|
project(fmt-test)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#ifndef FMT_FORMAT_
|
#ifndef FMT_FORMAT_
|
||||||
#define FMT_FORMAT_
|
#define FMT_FORMAT_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
@ -38,9 +39,9 @@ namespace std {
|
||||||
|
|
||||||
template<class Out, class charT> class basic_format_context;
|
template<class Out, class charT> class basic_format_context;
|
||||||
using format_context = basic_format_context<
|
using format_context = basic_format_context<
|
||||||
/* unspecified */ std::back_insert_iterator<fmt::detail::buffer<char>>, char>;
|
/* unspecified */ fmt::detail::buffer_appender<char>, char>;
|
||||||
using wformat_context = basic_format_context<
|
using wformat_context = basic_format_context<
|
||||||
/* unspecified */ std::back_insert_iterator<fmt::detail::buffer<wchar_t>>, wchar_t>;
|
/* unspecified */ fmt::detail::buffer_appender<wchar_t>, wchar_t>;
|
||||||
|
|
||||||
template<class T, class charT = char> struct formatter {
|
template<class T, class charT = char> struct formatter {
|
||||||
formatter() = delete;
|
formatter() = delete;
|
||||||
|
@ -714,7 +715,7 @@ string vformat(string_view fmt, format_args args) {
|
||||||
fmt::detail::buffer<char>& buf = mbuf;
|
fmt::detail::buffer<char>& buf = mbuf;
|
||||||
using af = detail::arg_formatter<fmt::format_context::iterator, char>;
|
using af = detail::arg_formatter<fmt::format_context::iterator, char>;
|
||||||
detail::format_handler<af, char, format_context>
|
detail::format_handler<af, char, format_context>
|
||||||
h(std::back_inserter(buf), fmt, args, {});
|
h(fmt::detail::buffer_appender<char>(buf), fmt, args, {});
|
||||||
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
|
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
|
||||||
return to_string(mbuf);
|
return to_string(mbuf);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Copyright (c) 2020 Vladimir Solontsov
|
|
||||||
// SPDX-License-Identifier: MIT Licence
|
|
||||||
|
|
||||||
#include <fmt/core.h>
|
|
||||||
|
|
||||||
#include "gtest-extra.h"
|
|
|
@ -5,21 +5,16 @@
|
||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#define FMT_NOEXCEPT
|
|
||||||
#undef FMT_SHARED
|
|
||||||
#include "test-assert.h"
|
|
||||||
|
|
||||||
// Include format.cc instead of format.h to test implementation.
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "../src/format.cc"
|
// clang-format off
|
||||||
#include "fmt/printf.h"
|
#include "test-assert.h"
|
||||||
#include "gmock.h"
|
// clang-format on
|
||||||
#include "gtest-extra.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#undef max
|
#include "fmt/format.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
using fmt::detail::bigint;
|
using fmt::detail::bigint;
|
||||||
using fmt::detail::fp;
|
using fmt::detail::fp;
|
||||||
|
@ -28,13 +23,13 @@ using fmt::detail::max_value;
|
||||||
static_assert(!std::is_copy_constructible<bigint>::value, "");
|
static_assert(!std::is_copy_constructible<bigint>::value, "");
|
||||||
static_assert(!std::is_copy_assignable<bigint>::value, "");
|
static_assert(!std::is_copy_assignable<bigint>::value, "");
|
||||||
|
|
||||||
TEST(BigIntTest, Construct) {
|
TEST(bigint_test, construct) {
|
||||||
EXPECT_EQ("", fmt::format("{}", bigint()));
|
EXPECT_EQ("", fmt::format("{}", bigint()));
|
||||||
EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
|
EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
|
||||||
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
|
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, Compare) {
|
TEST(bigint_test, compare) {
|
||||||
bigint n1(42);
|
bigint n1(42);
|
||||||
bigint n2(42);
|
bigint n2(42);
|
||||||
EXPECT_EQ(compare(n1, n2), 0);
|
EXPECT_EQ(compare(n1, n2), 0);
|
||||||
|
@ -48,7 +43,7 @@ TEST(BigIntTest, Compare) {
|
||||||
EXPECT_GT(compare(n4, n2), 0);
|
EXPECT_GT(compare(n4, n2), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, AddCompare) {
|
TEST(bigint_test, add_compare) {
|
||||||
EXPECT_LT(
|
EXPECT_LT(
|
||||||
add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
|
add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
|
||||||
EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
|
EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
|
||||||
|
@ -74,7 +69,7 @@ TEST(BigIntTest, AddCompare) {
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, ShiftLeft) {
|
TEST(bigint_test, shift_left) {
|
||||||
bigint n(0x42);
|
bigint n(0x42);
|
||||||
n <<= 0;
|
n <<= 0;
|
||||||
EXPECT_EQ("42", fmt::format("{}", n));
|
EXPECT_EQ("42", fmt::format("{}", n));
|
||||||
|
@ -84,7 +79,7 @@ TEST(BigIntTest, ShiftLeft) {
|
||||||
EXPECT_EQ("108000000", fmt::format("{}", n));
|
EXPECT_EQ("108000000", fmt::format("{}", n));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, Multiply) {
|
TEST(bigint_test, multiply) {
|
||||||
bigint n(0x42);
|
bigint n(0x42);
|
||||||
EXPECT_THROW(n *= 0, assertion_failure);
|
EXPECT_THROW(n *= 0, assertion_failure);
|
||||||
n *= 1;
|
n *= 1;
|
||||||
|
@ -101,7 +96,7 @@ TEST(BigIntTest, Multiply) {
|
||||||
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
|
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, Accumulator) {
|
TEST(bigint_test, accumulator) {
|
||||||
fmt::detail::accumulator acc;
|
fmt::detail::accumulator acc;
|
||||||
EXPECT_EQ(acc.lower, 0);
|
EXPECT_EQ(acc.lower, 0);
|
||||||
EXPECT_EQ(acc.upper, 0);
|
EXPECT_EQ(acc.upper, 0);
|
||||||
|
@ -110,7 +105,7 @@ TEST(BigIntTest, Accumulator) {
|
||||||
EXPECT_EQ(static_cast<uint32_t>(acc), 34);
|
EXPECT_EQ(static_cast<uint32_t>(acc), 34);
|
||||||
acc += 56;
|
acc += 56;
|
||||||
EXPECT_EQ(acc.lower, 90);
|
EXPECT_EQ(acc.lower, 90);
|
||||||
acc += fmt::detail::max_value<uint64_t>();
|
acc += max_value<uint64_t>();
|
||||||
EXPECT_EQ(acc.upper, 13);
|
EXPECT_EQ(acc.upper, 13);
|
||||||
EXPECT_EQ(acc.lower, 89);
|
EXPECT_EQ(acc.lower, 89);
|
||||||
acc >>= 32;
|
acc >>= 32;
|
||||||
|
@ -118,7 +113,7 @@ TEST(BigIntTest, Accumulator) {
|
||||||
EXPECT_EQ(acc.lower, 13 * 0x100000000);
|
EXPECT_EQ(acc.lower, 13 * 0x100000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, Square) {
|
TEST(bigint_test, square) {
|
||||||
bigint n0(0);
|
bigint n0(0);
|
||||||
n0.square();
|
n0.square();
|
||||||
EXPECT_EQ("0", fmt::format("{}", n0));
|
EXPECT_EQ("0", fmt::format("{}", n0));
|
||||||
|
@ -136,18 +131,18 @@ TEST(BigIntTest, Square) {
|
||||||
EXPECT_EQ("2540be400", fmt::format("{}", n4));
|
EXPECT_EQ("2540be400", fmt::format("{}", n4));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, DivModAssignZeroDivisor) {
|
TEST(bigint_test, divmod_assign_zero_divisor) {
|
||||||
bigint zero(0);
|
bigint zero(0);
|
||||||
EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
|
EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
|
||||||
EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
|
EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, DivModAssignSelf) {
|
TEST(bigint_test, divmod_assign_self) {
|
||||||
bigint n(100);
|
bigint n(100);
|
||||||
EXPECT_THROW(n.divmod_assign(n), assertion_failure);
|
EXPECT_THROW(n.divmod_assign(n), assertion_failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, DivModAssignUnaligned) {
|
TEST(bigint_test, divmod_assign_unaligned) {
|
||||||
// (42 << 340) / pow(10, 100):
|
// (42 << 340) / pow(10, 100):
|
||||||
bigint n1(42);
|
bigint n1(42);
|
||||||
n1 <<= 340;
|
n1 <<= 340;
|
||||||
|
@ -159,7 +154,7 @@ TEST(BigIntTest, DivModAssignUnaligned) {
|
||||||
fmt::format("{}", n1));
|
fmt::format("{}", n1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, DivModAssign) {
|
TEST(bigint_test, divmod_assign) {
|
||||||
// 100 / 10:
|
// 100 / 10:
|
||||||
bigint n1(100);
|
bigint n1(100);
|
||||||
int result = n1.divmod_assign(bigint(10));
|
int result = n1.divmod_assign(bigint(10));
|
||||||
|
@ -186,70 +181,20 @@ template <bool is_iec559> void run_double_tests() {
|
||||||
template <> void run_double_tests<true>() {
|
template <> void run_double_tests<true>() {
|
||||||
// Construct from double.
|
// Construct from double.
|
||||||
EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
|
EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
|
||||||
|
|
||||||
// Compute boundaries:
|
|
||||||
fp value;
|
|
||||||
// Normalized & not power of 2 - equidistant boundaries:
|
|
||||||
auto b = value.assign_with_boundaries(1.23);
|
|
||||||
EXPECT_EQ(value, fp(0x0013ae147ae147ae, -52));
|
|
||||||
EXPECT_EQ(b.lower, 0x9d70a3d70a3d6c00);
|
|
||||||
EXPECT_EQ(b.upper, 0x9d70a3d70a3d7400);
|
|
||||||
// Normalized power of 2 - lower boundary is closer:
|
|
||||||
b = value.assign_with_boundaries(1.9807040628566084e+28); // 2**94
|
|
||||||
EXPECT_EQ(value, fp(0x0010000000000000, 42));
|
|
||||||
EXPECT_EQ(b.lower, 0x7ffffffffffffe00);
|
|
||||||
EXPECT_EQ(b.upper, 0x8000000000000400);
|
|
||||||
// Smallest normalized double - equidistant boundaries:
|
|
||||||
b = value.assign_with_boundaries(2.2250738585072014e-308);
|
|
||||||
EXPECT_EQ(value, fp(0x0010000000000000, -1074));
|
|
||||||
EXPECT_EQ(b.lower, 0x7ffffffffffffc00);
|
|
||||||
EXPECT_EQ(b.upper, 0x8000000000000400);
|
|
||||||
// Subnormal - equidistant boundaries:
|
|
||||||
b = value.assign_with_boundaries(4.9406564584124654e-324);
|
|
||||||
EXPECT_EQ(value, fp(0x0000000000000001, -1074));
|
|
||||||
EXPECT_EQ(b.lower, 0x4000000000000000);
|
|
||||||
EXPECT_EQ(b.upper, 0xc000000000000000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, DoubleTests) {
|
TEST(fp_test, double_tests) {
|
||||||
run_double_tests<std::numeric_limits<double>::is_iec559>();
|
run_double_tests<std::numeric_limits<double>::is_iec559>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, Normalize) {
|
TEST(fp_test, normalize) {
|
||||||
const auto v = fp(0xbeef, 42);
|
const auto v = fp(0xbeef, 42);
|
||||||
auto normalized = normalize(v);
|
auto normalized = normalize(v);
|
||||||
EXPECT_EQ(0xbeef000000000000, normalized.f);
|
EXPECT_EQ(0xbeef000000000000, normalized.f);
|
||||||
EXPECT_EQ(-6, normalized.e);
|
EXPECT_EQ(-6, normalized.e);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, ComputeFloatBoundaries) {
|
TEST(fp_test, multiply) {
|
||||||
struct {
|
|
||||||
double x, lower, upper;
|
|
||||||
} tests[] = {
|
|
||||||
// regular
|
|
||||||
{1.5f, 1.4999999403953552, 1.5000000596046448},
|
|
||||||
// boundary
|
|
||||||
{1.0f, 0.9999999701976776, 1.0000000596046448},
|
|
||||||
// min normal
|
|
||||||
{1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38},
|
|
||||||
// max subnormal
|
|
||||||
{1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38},
|
|
||||||
// min subnormal
|
|
||||||
{1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45},
|
|
||||||
};
|
|
||||||
for (auto test : tests) {
|
|
||||||
fp vlower = normalize(fp(test.lower));
|
|
||||||
fp vupper = normalize(fp(test.upper));
|
|
||||||
vlower.f >>= vupper.e - vlower.e;
|
|
||||||
vlower.e = vupper.e;
|
|
||||||
fp value;
|
|
||||||
auto b = value.assign_float_with_boundaries(test.x);
|
|
||||||
EXPECT_EQ(vlower.f, b.lower);
|
|
||||||
EXPECT_EQ(vupper.f, b.upper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FPTest, Multiply) {
|
|
||||||
auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
|
auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
|
||||||
EXPECT_EQ(v.f, 123u * 56u);
|
EXPECT_EQ(v.f, 123u * 56u);
|
||||||
EXPECT_EQ(v.e, 4 + 7 + 64);
|
EXPECT_EQ(v.e, 4 + 7 + 64);
|
||||||
|
@ -258,19 +203,57 @@ TEST(FPTest, Multiply) {
|
||||||
EXPECT_EQ(v.e, 4 + 8 + 64);
|
EXPECT_EQ(v.e, 4 + 8 + 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, GetCachedPower) {
|
TEST(fp_test, get_cached_power) {
|
||||||
typedef std::numeric_limits<double> limits;
|
using limits = std::numeric_limits<double>;
|
||||||
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
|
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
|
||||||
int dec_exp = 0;
|
int dec_exp = 0;
|
||||||
auto fp = fmt::detail::get_cached_power(exp, dec_exp);
|
auto fp = fmt::detail::get_cached_power(exp, dec_exp);
|
||||||
EXPECT_LE(exp, fp.e);
|
bigint exact, cache(fp.f);
|
||||||
int dec_exp_step = 8;
|
if (dec_exp >= 0) {
|
||||||
EXPECT_LE(fp.e, exp + dec_exp_step * log2(10));
|
exact.assign_pow10(dec_exp);
|
||||||
EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(static_cast<double>(fp.f), fp.e));
|
if (fp.e <= 0)
|
||||||
|
exact <<= -fp.e;
|
||||||
|
else
|
||||||
|
cache <<= fp.e;
|
||||||
|
exact.align(cache);
|
||||||
|
cache.align(exact);
|
||||||
|
auto exact_str = fmt::format("{}", exact);
|
||||||
|
auto cache_str = fmt::format("{}", 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 *= fp.f + 1; // Inexact check.
|
||||||
|
exact.assign(1);
|
||||||
|
exact <<= -fp.e;
|
||||||
|
exact.align(cache);
|
||||||
|
auto exact_str = fmt::format("{}", exact);
|
||||||
|
auto cache_str = fmt::format("{}", cache);
|
||||||
|
EXPECT_EQ(exact_str.size(), cache_str.size());
|
||||||
|
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, GetRoundDirection) {
|
TEST(fp_test, dragonbox_max_k) {
|
||||||
|
using fmt::detail::dragonbox::floor_log10_pow2;
|
||||||
|
using float_info = fmt::detail::dragonbox::float_info<float>;
|
||||||
|
EXPECT_EQ(fmt::detail::const_check(float_info::max_k),
|
||||||
|
float_info::kappa - floor_log10_pow2(float_info::min_exponent -
|
||||||
|
float_info::significand_bits));
|
||||||
|
using double_info = fmt::detail::dragonbox::float_info<double>;
|
||||||
|
EXPECT_EQ(
|
||||||
|
fmt::detail::const_check(double_info::max_k),
|
||||||
|
double_info::kappa - floor_log10_pow2(double_info::min_exponent -
|
||||||
|
double_info::significand_bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(fp_test, get_round_direction) {
|
||||||
using fmt::detail::get_round_direction;
|
using fmt::detail::get_round_direction;
|
||||||
using fmt::detail::round_direction;
|
using fmt::detail::round_direction;
|
||||||
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
|
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
|
||||||
|
@ -294,7 +277,7 @@ TEST(FPTest, GetRoundDirection) {
|
||||||
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
|
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, FixedHandler) {
|
TEST(fp_test, fixed_handler) {
|
||||||
struct handler : fmt::detail::fixed_handler {
|
struct handler : fmt::detail::fixed_handler {
|
||||||
char buffer[10];
|
char buffer[10];
|
||||||
handler(int prec = 0) : fmt::detail::fixed_handler() {
|
handler(int prec = 0) : fmt::detail::fixed_handler() {
|
||||||
|
@ -307,7 +290,7 @@ TEST(FPTest, FixedHandler) {
|
||||||
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
|
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
|
||||||
assertion_failure);
|
assertion_failure);
|
||||||
namespace digits = fmt::detail::digits;
|
namespace digits = fmt::detail::digits;
|
||||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done);
|
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error);
|
||||||
// Check that divisor - error doesn't overflow.
|
// Check that divisor - error doesn't overflow.
|
||||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
|
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
|
||||||
// Check that 2 * error doesn't overflow.
|
// Check that 2 * error doesn't overflow.
|
||||||
|
@ -316,94 +299,23 @@ TEST(FPTest, FixedHandler) {
|
||||||
digits::error);
|
digits::error);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
|
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
|
||||||
fmt::memory_buffer buf;
|
fmt::memory_buffer buf;
|
||||||
format_float(0.42, -1, fmt::detail::float_specs(), buf);
|
format_float(0.42, -1, fmt::detail::float_specs(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct value_extractor {
|
TEST(format_impl_test, format_error_code) {
|
||||||
T operator()(T value) { return value; }
|
|
||||||
|
|
||||||
template <typename U> FMT_NORETURN T operator()(U) {
|
|
||||||
throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_INT128
|
|
||||||
// Apple Clang does not define typeid for __int128_t and __uint128_t.
|
|
||||||
FMT_NORETURN T operator()(fmt::detail::int128_t) {
|
|
||||||
throw std::runtime_error("invalid type __int128_t");
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_NORETURN T operator()(fmt::detail::uint128_t) {
|
|
||||||
throw std::runtime_error("invalid type __uint128_t");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(FormatTest, ArgConverter) {
|
|
||||||
long long value = max_value<long long>();
|
|
||||||
auto arg = fmt::detail::make_arg<fmt::format_context>(value);
|
|
||||||
fmt::visit_format_arg(
|
|
||||||
fmt::detail::arg_converter<long long, fmt::format_context>(arg, 'd'),
|
|
||||||
arg);
|
|
||||||
EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatTest, FormatNegativeNaN) {
|
|
||||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
|
||||||
if (std::signbit(-nan))
|
|
||||||
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
|
||||||
else
|
|
||||||
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatTest, StrError) {
|
|
||||||
char* message = nullptr;
|
|
||||||
char buffer[BUFFER_SIZE];
|
|
||||||
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0),
|
|
||||||
"invalid buffer");
|
|
||||||
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0),
|
|
||||||
"invalid buffer");
|
|
||||||
buffer[0] = 'x';
|
|
||||||
#if defined(_GNU_SOURCE) && !defined(__COVERITY__)
|
|
||||||
// Use invalid error code to make sure that safe_strerror returns an error
|
|
||||||
// message in the buffer rather than a pointer to a static string.
|
|
||||||
int error_code = -1;
|
|
||||||
#else
|
|
||||||
int error_code = EDOM;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int result =
|
|
||||||
fmt::detail::safe_strerror(error_code, message = buffer, BUFFER_SIZE);
|
|
||||||
EXPECT_EQ(result, 0);
|
|
||||||
size_t message_size = std::strlen(message);
|
|
||||||
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
|
|
||||||
EXPECT_EQ(get_system_error(error_code), message);
|
|
||||||
|
|
||||||
// safe_strerror never uses buffer on MinGW.
|
|
||||||
#if !defined(__MINGW32__) && !defined(__sun)
|
|
||||||
result =
|
|
||||||
fmt::detail::safe_strerror(error_code, message = buffer, message_size);
|
|
||||||
EXPECT_EQ(ERANGE, result);
|
|
||||||
result = fmt::detail::safe_strerror(error_code, message = buffer, 1);
|
|
||||||
EXPECT_EQ(buffer, message); // Message should point to buffer.
|
|
||||||
EXPECT_EQ(ERANGE, result);
|
|
||||||
EXPECT_STREQ("", message);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatTest, FormatErrorCode) {
|
|
||||||
std::string msg = "error 42", sep = ": ";
|
std::string msg = "error 42", sep = ": ";
|
||||||
{
|
{
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
format_to(buffer, "garbage");
|
format_to(fmt::appender(buffer), "garbage");
|
||||||
fmt::detail::format_error_code(buffer, 42, "test");
|
fmt::detail::format_error_code(buffer, 42, "test");
|
||||||
EXPECT_EQ("test: " + msg, to_string(buffer));
|
EXPECT_EQ("test: " + msg, to_string(buffer));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size() + 1,
|
auto prefix =
|
||||||
'x');
|
std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x');
|
||||||
fmt::detail::format_error_code(buffer, 42, prefix);
|
fmt::detail::format_error_code(buffer, 42, prefix);
|
||||||
EXPECT_EQ(msg, to_string(buffer));
|
EXPECT_EQ(msg, to_string(buffer));
|
||||||
}
|
}
|
||||||
|
@ -412,7 +324,8 @@ TEST(FormatTest, FormatErrorCode) {
|
||||||
// Test maximum buffer size.
|
// Test maximum buffer size.
|
||||||
msg = fmt::format("error {}", codes[i]);
|
msg = fmt::format("error {}", codes[i]);
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
|
auto prefix =
|
||||||
|
std::string(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
|
||||||
fmt::detail::format_error_code(buffer, codes[i], prefix);
|
fmt::detail::format_error_code(buffer, codes[i], prefix);
|
||||||
EXPECT_EQ(prefix + sep + msg, to_string(buffer));
|
EXPECT_EQ(prefix + sep + msg, to_string(buffer));
|
||||||
size_t size = fmt::inline_buffer_size;
|
size_t size = fmt::inline_buffer_size;
|
||||||
|
@ -425,9 +338,9 @@ TEST(FormatTest, FormatErrorCode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FormatTest, CountCodePoints) {
|
TEST(format_impl_test, compute_width) {
|
||||||
EXPECT_EQ(4,
|
EXPECT_EQ(4,
|
||||||
fmt::detail::count_code_points(
|
fmt::detail::compute_width(
|
||||||
fmt::basic_string_view<fmt::detail::char8_type>(
|
fmt::basic_string_view<fmt::detail::char8_type>(
|
||||||
reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
|
reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
|
||||||
}
|
}
|
||||||
|
@ -442,15 +355,26 @@ template <typename Int> void test_count_digits() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, CountDigits) {
|
TEST(format_impl_test, count_digits) {
|
||||||
test_count_digits<uint32_t>();
|
test_count_digits<uint32_t>();
|
||||||
test_count_digits<uint64_t>();
|
test_count_digits<uint64_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, WriteFallbackUIntPtr) {
|
TEST(format_impl_test, write_fallback_uintptr) {
|
||||||
std::string s;
|
std::string s;
|
||||||
fmt::detail::write_ptr<char>(
|
fmt::detail::write_ptr<char>(
|
||||||
std::back_inserter(s),
|
std::back_inserter(s),
|
||||||
fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr);
|
fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr);
|
||||||
EXPECT_EQ(s, "0xface");
|
EXPECT_EQ(s, "0xface");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
TEST(format_impl_test, write_console_signature) {
|
||||||
|
decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW;
|
||||||
|
(void)p;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
2757
test/format-test.cc
2757
test/format-test.cc
File diff suppressed because it is too large
Load diff
|
@ -1,38 +1,30 @@
|
||||||
# Copyright (c) 2019, Paul Dreik
|
# Copyright (c) 2019, Paul Dreik
|
||||||
# License: see LICENSE.rst in the fmt root directory
|
# License: see LICENSE.rst in the fmt root directory
|
||||||
|
|
||||||
# settings this links in a main. useful for reproducing,
|
# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind.
|
||||||
# kcov, gdb, afl, valgrind.
|
# (Note that libFuzzer can also reproduce, just pass it the files.)
|
||||||
# (note that libFuzzer can also reproduce, just pass it the files)
|
option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On)
|
||||||
option(FMT_FUZZ_LINKMAIN "enables the reproduce mode, instead of libFuzzer" On)
|
|
||||||
|
|
||||||
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
|
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
|
||||||
# the fuzz targets, otherwise the cmake configuration step fails.
|
# the fuzz targets, otherwise the CMake configuration step fails.
|
||||||
set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
||||||
|
|
||||||
# Find all fuzzers.
|
# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data
|
||||||
set(SOURCES
|
# through the fuzzers.
|
||||||
chrono_duration.cpp
|
function(add_fuzzer source)
|
||||||
named_arg.cpp
|
get_filename_component(basename ${source} NAME_WE)
|
||||||
one_arg.cpp
|
set(name ${basename}-fuzzer)
|
||||||
sprintf.cpp
|
add_executable(${name} ${source} fuzzer-common.h)
|
||||||
two_args.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
macro(implement_fuzzer sourcefile)
|
|
||||||
get_filename_component(basename ${sourcefile} NAME_WE)
|
|
||||||
set(name fuzzer_${basename})
|
|
||||||
add_executable(${name} ${sourcefile} fuzzer_common.h)
|
|
||||||
if (FMT_FUZZ_LINKMAIN)
|
if (FMT_FUZZ_LINKMAIN)
|
||||||
target_sources(${name} PRIVATE main.cpp)
|
target_sources(${name} PRIVATE main.cc)
|
||||||
endif ()
|
endif ()
|
||||||
target_link_libraries(${name} PRIVATE fmt)
|
target_link_libraries(${name} PRIVATE fmt)
|
||||||
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_generic_lambdas)
|
||||||
endmacro ()
|
endfunction()
|
||||||
|
|
||||||
foreach (X IN ITEMS ${SOURCES})
|
foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc)
|
||||||
implement_fuzzer(${X})
|
add_fuzzer(${source})
|
||||||
endforeach ()
|
endforeach ()
|
||||||
|
|
|
@ -1,27 +1,4 @@
|
||||||
# FMT Fuzzer
|
# Running the fuzzers locally
|
||||||
|
|
||||||
Fuzzing has revealed [several bugs](https://github.com/fmtlib/fmt/issues?&q=is%3Aissue+fuzz)
|
|
||||||
in fmt. It is a part of the continous fuzzing at
|
|
||||||
[oss-fuzz](https://github.com/google/oss-fuzz).
|
|
||||||
|
|
||||||
The source code is modified to make the fuzzing possible without locking up on
|
|
||||||
resource exhaustion:
|
|
||||||
```cpp
|
|
||||||
#ifdef FMT_FUZZ
|
|
||||||
if(spec.precision>100000) {
|
|
||||||
throw std::runtime_error("fuzz mode - avoiding large precision");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
```
|
|
||||||
This macro `FMT_FUZZ` is enabled on OSS-Fuzz builds and makes fuzzing
|
|
||||||
practically possible. It is used in fmt code to prevent resource exhaustion in
|
|
||||||
fuzzing mode.
|
|
||||||
The macro `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` is the
|
|
||||||
defacto standard for making fuzzing practically possible to disable certain
|
|
||||||
fuzzing-unfriendly features (for example, randomness), see [the libFuzzer
|
|
||||||
documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode).
|
|
||||||
|
|
||||||
## Running the fuzzers locally
|
|
||||||
|
|
||||||
There is a [helper script](build.sh) to build the fuzzers, which has only been
|
There is a [helper script](build.sh) to build the fuzzers, which has only been
|
||||||
tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on
|
tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on
|
||||||
|
@ -34,7 +11,7 @@ mkdir build
|
||||||
cd build
|
cd build
|
||||||
export CXX=clang++
|
export CXX=clang++
|
||||||
export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
|
export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
|
||||||
cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||||
cmake --build .
|
cmake --build .
|
||||||
```
|
```
|
||||||
should work to build the fuzzers for all platforms which clang supports.
|
should work to build the fuzzers for all platforms which clang supports.
|
||||||
|
@ -44,5 +21,5 @@ Execute a fuzzer with for instance
|
||||||
cd build
|
cd build
|
||||||
export UBSAN_OPTIONS=halt_on_error=1
|
export UBSAN_OPTIONS=halt_on_error=1
|
||||||
mkdir out_chrono
|
mkdir out_chrono
|
||||||
bin/fuzzer_chrono_duration out_chrono
|
bin/fuzzer_chrono_duration out_chrono
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
# Creates fuzzer builds of various kinds
|
# Creates fuzzer builds of various kinds
|
||||||
# - reproduce mode (no fuzzing, just enables replaying data through the fuzzers)
|
|
||||||
# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
|
# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
|
||||||
# - libFuzzer build (you will need clang)
|
# - libFuzzer build (you will need clang)
|
||||||
# - afl build (you will need afl)
|
# - afl build (you will need afl)
|
||||||
|
@ -9,7 +8,7 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2019 Paul Dreik
|
# Copyright (c) 2019 Paul Dreik
|
||||||
#
|
#
|
||||||
# License: see LICENSE.rst in the fmt root directory
|
# For the license information refer to format.h.
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
me=$(basename $0)
|
me=$(basename $0)
|
||||||
|
@ -23,16 +22,7 @@ here=$(pwd)
|
||||||
CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
|
CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
|
||||||
CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
|
CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
|
||||||
|
|
||||||
#builds the fuzzers as one would do if using afl or just making
|
# For performance analysis of the fuzzers.
|
||||||
#binaries for reproducing.
|
|
||||||
builddir=$here/build-fuzzers-reproduce
|
|
||||||
mkdir -p $builddir
|
|
||||||
cd $builddir
|
|
||||||
CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL" cmake \
|
|
||||||
$CMAKEFLAGSALL
|
|
||||||
cmake --build $builddir
|
|
||||||
|
|
||||||
#for performance analysis of the fuzzers
|
|
||||||
builddir=$here/build-fuzzers-perfanalysis
|
builddir=$here/build-fuzzers-perfanalysis
|
||||||
mkdir -p $builddir
|
mkdir -p $builddir
|
||||||
cd $builddir
|
cd $builddir
|
||||||
|
@ -43,7 +33,7 @@ $CMAKEFLAGSALL \
|
||||||
|
|
||||||
cmake --build $builddir
|
cmake --build $builddir
|
||||||
|
|
||||||
#builds the fuzzers as oss-fuzz does
|
# Builds the fuzzers as oss-fuzz does.
|
||||||
builddir=$here/build-fuzzers-ossfuzz
|
builddir=$here/build-fuzzers-ossfuzz
|
||||||
mkdir -p $builddir
|
mkdir -p $builddir
|
||||||
cd $builddir
|
cd $builddir
|
||||||
|
@ -56,7 +46,7 @@ cmake $CMAKEFLAGSALL \
|
||||||
cmake --build $builddir
|
cmake --build $builddir
|
||||||
|
|
||||||
|
|
||||||
#builds fuzzers for local fuzzing with libfuzzer with asan+usan
|
# Builds fuzzers for local fuzzing with libfuzzer with asan+usan.
|
||||||
builddir=$here/build-fuzzers-libfuzzer
|
builddir=$here/build-fuzzers-libfuzzer
|
||||||
mkdir -p $builddir
|
mkdir -p $builddir
|
||||||
cd $builddir
|
cd $builddir
|
||||||
|
@ -68,19 +58,7 @@ cmake $CMAKEFLAGSALL \
|
||||||
|
|
||||||
cmake --build $builddir
|
cmake --build $builddir
|
||||||
|
|
||||||
#builds fuzzers for local fuzzing with libfuzzer with asan only
|
# Builds a fast fuzzer for making coverage fast.
|
||||||
builddir=$here/build-fuzzers-libfuzzer-addr
|
|
||||||
mkdir -p $builddir
|
|
||||||
cd $builddir
|
|
||||||
CXX="clang++" \
|
|
||||||
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,undefined" cmake \
|
|
||||||
cmake $CMAKEFLAGSALL \
|
|
||||||
-DFMT_FUZZ_LINKMAIN=Off \
|
|
||||||
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
|
||||||
|
|
||||||
cmake --build $builddir
|
|
||||||
|
|
||||||
#builds a fast fuzzer for making coverage fast
|
|
||||||
builddir=$here/build-fuzzers-fast
|
builddir=$here/build-fuzzers-fast
|
||||||
mkdir -p $builddir
|
mkdir -p $builddir
|
||||||
cd $builddir
|
cd $builddir
|
||||||
|
@ -94,7 +72,7 @@ cmake $CMAKEFLAGSALL \
|
||||||
cmake --build $builddir
|
cmake --build $builddir
|
||||||
|
|
||||||
|
|
||||||
#builds fuzzers for local fuzzing with afl
|
# Builds fuzzers for local fuzzing with afl.
|
||||||
builddir=$here/build-fuzzers-afl
|
builddir=$here/build-fuzzers-afl
|
||||||
mkdir -p $builddir
|
mkdir -p $builddir
|
||||||
cd $builddir
|
cd $builddir
|
||||||
|
|
135
test/fuzzing/chrono-duration.cc
Normal file
135
test/fuzzing/chrono-duration.cc
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) 2019, Paul Dreik
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
#include "fuzzer-common.h"
|
||||||
|
|
||||||
|
template <typename Period, typename Rep>
|
||||||
|
void invoke_inner(fmt::string_view format_str, Rep rep) {
|
||||||
|
auto value = std::chrono::duration<Rep, Period>(rep);
|
||||||
|
try {
|
||||||
|
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||||
|
std::string message = fmt::format(format_str, value);
|
||||||
|
#else
|
||||||
|
fmt::memory_buffer buf;
|
||||||
|
fmt::format_to(buf, format_str, value);
|
||||||
|
#endif
|
||||||
|
} catch (std::exception&) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rep is a duration's representation type.
|
||||||
|
template <typename Rep>
|
||||||
|
void invoke_outer(const uint8_t* data, size_t size, int period) {
|
||||||
|
// Always use a fixed location of the data.
|
||||||
|
static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small");
|
||||||
|
if (size <= fixed_size + 1) return;
|
||||||
|
|
||||||
|
const Rep rep = assign_from_buf<Rep>(data);
|
||||||
|
data += fixed_size;
|
||||||
|
size -= fixed_size;
|
||||||
|
|
||||||
|
// data is already allocated separately in libFuzzer so reading past the end
|
||||||
|
// will most likely be detected anyway.
|
||||||
|
const auto format_str = fmt::string_view(as_chars(data), size);
|
||||||
|
|
||||||
|
// yocto, zepto, zetta and yotta are not handled.
|
||||||
|
switch (period) {
|
||||||
|
case 1:
|
||||||
|
invoke_inner<std::atto>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
invoke_inner<std::femto>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
invoke_inner<std::pico>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
invoke_inner<std::nano>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
invoke_inner<std::micro>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
invoke_inner<std::milli>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
invoke_inner<std::centi>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
invoke_inner<std::deci>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
invoke_inner<std::deca>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
invoke_inner<std::kilo>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
invoke_inner<std::mega>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
invoke_inner<std::giga>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
invoke_inner<std::tera>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
invoke_inner<std::peta>(format_str, rep);
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
invoke_inner<std::exa>(format_str, rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
if (size <= 4) return 0;
|
||||||
|
|
||||||
|
const auto representation = data[0];
|
||||||
|
const auto period = data[1];
|
||||||
|
data += 2;
|
||||||
|
size -= 2;
|
||||||
|
|
||||||
|
switch (representation) {
|
||||||
|
case 1:
|
||||||
|
invoke_outer<char>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
invoke_outer<signed char>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
invoke_outer<unsigned char>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
invoke_outer<short>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
invoke_outer<unsigned short>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
invoke_outer<int>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
invoke_outer<unsigned int>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
invoke_outer<long>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
invoke_outer<unsigned long>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
invoke_outer<float>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
invoke_outer<double>(data, size, period);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
invoke_outer<long double>(data, size, period);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,152 +0,0 @@
|
||||||
// Copyright (c) 2019, Paul Dreik
|
|
||||||
// License: see LICENSE.rst in the fmt root directory
|
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
#include "fuzzer_common.h"
|
|
||||||
|
|
||||||
template <typename Item, typename Ratio>
|
|
||||||
void invoke_inner(fmt::string_view formatstring, const Item item) {
|
|
||||||
const std::chrono::duration<Item, Ratio> value(item);
|
|
||||||
try {
|
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
|
||||||
std::string message = fmt::format(formatstring, value);
|
|
||||||
#else
|
|
||||||
fmt::memory_buffer buf;
|
|
||||||
fmt::format_to(buf, formatstring, value);
|
|
||||||
#endif
|
|
||||||
} catch (std::exception& /*e*/) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Item is the underlying type for duration (int, long etc)
|
|
||||||
template <typename Item>
|
|
||||||
void invoke_outer(const uint8_t* Data, size_t Size, const int scaling) {
|
|
||||||
// always use a fixed location of the data
|
|
||||||
using fmt_fuzzer::Nfixed;
|
|
||||||
|
|
||||||
constexpr auto N = sizeof(Item);
|
|
||||||
static_assert(N <= Nfixed, "fixed size is too small");
|
|
||||||
if (Size <= Nfixed + 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
|
|
||||||
|
|
||||||
// fast forward
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
|
|
||||||
// Data is already allocated separately in libFuzzer so reading past
|
|
||||||
// the end will most likely be detected anyway
|
|
||||||
const auto formatstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
|
||||||
|
|
||||||
// doit_impl<Item,std::yocto>(buf.data(),item);
|
|
||||||
// doit_impl<Item,std::zepto>(buf.data(),item);
|
|
||||||
switch (scaling) {
|
|
||||||
case 1:
|
|
||||||
invoke_inner<Item, std::atto>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
invoke_inner<Item, std::femto>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
invoke_inner<Item, std::pico>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
invoke_inner<Item, std::nano>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
invoke_inner<Item, std::micro>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
invoke_inner<Item, std::milli>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
invoke_inner<Item, std::centi>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
invoke_inner<Item, std::deci>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
invoke_inner<Item, std::deca>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
invoke_inner<Item, std::kilo>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
invoke_inner<Item, std::mega>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
invoke_inner<Item, std::giga>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
invoke_inner<Item, std::tera>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 14:
|
|
||||||
invoke_inner<Item, std::peta>(formatstring, item);
|
|
||||||
break;
|
|
||||||
case 15:
|
|
||||||
invoke_inner<Item, std::exa>(formatstring, item);
|
|
||||||
}
|
|
||||||
// doit_impl<Item,std::zeta>(buf.data(),item);
|
|
||||||
// doit_impl<Item,std::yotta>(buf.data(),item);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
|
||||||
if (Size <= 4) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto representation = Data[0];
|
|
||||||
const auto scaling = Data[1];
|
|
||||||
Data += 2;
|
|
||||||
Size -= 2;
|
|
||||||
|
|
||||||
switch (representation) {
|
|
||||||
case 1:
|
|
||||||
invoke_outer<char>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
invoke_outer<unsigned char>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
invoke_outer<signed char>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
invoke_outer<short>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
invoke_outer<unsigned short>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
invoke_outer<int>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
invoke_outer<unsigned int>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
invoke_outer<long>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
invoke_outer<unsigned long>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
invoke_outer<float>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
invoke_outer<double>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
invoke_outer<long double>(Data, Size, scaling);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
39
test/fuzzing/float.cc
Normal file
39
test/fuzzing/float.cc
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// A fuzzer for floating-point formatter.
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <limits>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "fuzzer-common.h"
|
||||||
|
|
||||||
|
void check_round_trip(fmt::string_view format_str, double value) {
|
||||||
|
auto buffer = fmt::memory_buffer();
|
||||||
|
fmt::format_to(buffer, format_str, value);
|
||||||
|
|
||||||
|
if (std::isnan(value)) {
|
||||||
|
auto nan = std::signbit(value) ? "-nan" : "nan";
|
||||||
|
if (fmt::string_view(buffer.data(), buffer.size()) != nan)
|
||||||
|
throw std::runtime_error("round trip failure");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.push_back('\0');
|
||||||
|
char* ptr = nullptr;
|
||||||
|
if (std::strtod(buffer.data(), &ptr) != value)
|
||||||
|
throw std::runtime_error("round trip failure");
|
||||||
|
if (ptr + 1 != buffer.end())
|
||||||
|
throw std::runtime_error("unparsed output");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559)
|
||||||
|
return 0;
|
||||||
|
check_round_trip("{}", assign_from_buf<double>(data));
|
||||||
|
// A larger than necessary precision is used to trigger the fallback
|
||||||
|
// formatter.
|
||||||
|
check_round_trip("{:.50g}", assign_from_buf<double>(data));
|
||||||
|
return 0;
|
||||||
|
}
|
75
test/fuzzing/fuzzer-common.h
Normal file
75
test/fuzzing/fuzzer-common.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2019, Paul Dreik
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FUZZER_COMMON_H
|
||||||
|
#define FUZZER_COMMON_H
|
||||||
|
|
||||||
|
#include <cstdint> // std::uint8_t
|
||||||
|
#include <cstring> // memcpy
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
// One can format to either a string, or a buffer. The latter is faster, but
|
||||||
|
// one may be interested in formatting to a string instead to verify it works
|
||||||
|
// as intended. To avoid a combinatoric explosion, select this at compile time
|
||||||
|
// instead of dynamically from the fuzz data.
|
||||||
|
#define FMT_FUZZ_FORMAT_TO_STRING 0
|
||||||
|
|
||||||
|
// If {fmt} is given a buffer that is separately allocated, chances that address
|
||||||
|
// sanitizer detects out of bound reads is much higher. However, it slows down
|
||||||
|
// the fuzzing.
|
||||||
|
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
||||||
|
|
||||||
|
// The size of the largest possible type in use.
|
||||||
|
// To let the the fuzzer mutation be efficient at cross pollinating between
|
||||||
|
// different types, use a fixed size format. The same bit pattern, interpreted
|
||||||
|
// as another type, is likely interesting.
|
||||||
|
constexpr auto fixed_size = 16;
|
||||||
|
|
||||||
|
// Casts data to a char pointer.
|
||||||
|
template <typename T> inline const char* as_chars(const T* data) {
|
||||||
|
return reinterpret_cast<const char*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Casts data to a byte pointer.
|
||||||
|
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||||
|
return reinterpret_cast<const std::uint8_t*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blits bytes from data to form an (assumed trivially constructible) object
|
||||||
|
// of type Item.
|
||||||
|
template <class Item> inline Item assign_from_buf(const std::uint8_t* data) {
|
||||||
|
auto item = Item();
|
||||||
|
std::memcpy(&item, data, sizeof(Item));
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads a boolean value by looking at the first byte from data.
|
||||||
|
template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) {
|
||||||
|
return *data != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct data_to_string {
|
||||||
|
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
||||||
|
std::vector<char> buffer;
|
||||||
|
|
||||||
|
data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
|
||||||
|
: buffer(size + (add_terminator ? 1 : 0)) {
|
||||||
|
std::memcpy(buffer.data(), data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
|
||||||
|
#else
|
||||||
|
fmt::string_view sv;
|
||||||
|
|
||||||
|
data_to_string(const uint8_t* data, size_t size, bool = false)
|
||||||
|
: str(as_chars(data), size) {}
|
||||||
|
|
||||||
|
fmt::string_view get() const { return sv; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* data() const { return get().data(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FUZZER_COMMON_H
|
|
@ -1,67 +0,0 @@
|
||||||
#ifndef FUZZER_COMMON_H
|
|
||||||
#define FUZZER_COMMON_H
|
|
||||||
|
|
||||||
// Copyright (c) 2019, Paul Dreik
|
|
||||||
// License: see LICENSE.rst in the fmt root directory
|
|
||||||
|
|
||||||
#include <cstdint> // std::uint8_t
|
|
||||||
#include <cstring> // memcpy
|
|
||||||
#include <type_traits> // trivially copyable
|
|
||||||
|
|
||||||
// one can format to either a string, or a buf. buf is faster,
|
|
||||||
// but one may be interested in formatting to a string instead to
|
|
||||||
// verify it works as intended. to avoid a combinatoric explosion,
|
|
||||||
// select this at compile time instead of dynamically from the fuzz data
|
|
||||||
#define FMT_FUZZ_FORMAT_TO_STRING 0
|
|
||||||
|
|
||||||
// if fmt is given a buffer that is separately allocated,
|
|
||||||
// chances that address sanitizer detects out of bound reads is
|
|
||||||
// much higher. However, it slows down the fuzzing.
|
|
||||||
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
|
|
||||||
|
|
||||||
// To let the the fuzzer mutation be efficient at cross pollinating
|
|
||||||
// between different types, use a fixed size format.
|
|
||||||
// The same bit pattern, interpreted as another type,
|
|
||||||
// is likely interesting.
|
|
||||||
// For this, we must know the size of the largest possible type in use.
|
|
||||||
|
|
||||||
// There are some problems on travis, claiming Nfixed is not a constant
|
|
||||||
// expression which seems to be an issue with older versions of libstdc++
|
|
||||||
#if _GLIBCXX_RELEASE >= 7
|
|
||||||
# include <algorithm>
|
|
||||||
namespace fmt_fuzzer {
|
|
||||||
constexpr auto Nfixed = std::max(sizeof(long double), sizeof(std::intmax_t));
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
namespace fmt_fuzzer {
|
|
||||||
constexpr auto Nfixed = 16;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace fmt_fuzzer {
|
|
||||||
// view data as a c char pointer.
|
|
||||||
template <typename T> inline const char* as_chars(const T* data) {
|
|
||||||
return static_cast<const char*>(static_cast<const void*>(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
// view data as a byte pointer
|
|
||||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
|
||||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
// blits bytes from Data to form an (assumed trivially constructible) object
|
|
||||||
// of type Item
|
|
||||||
template <class Item> inline Item assignFromBuf(const std::uint8_t* Data) {
|
|
||||||
Item item{};
|
|
||||||
std::memcpy(&item, Data, sizeof(Item));
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reads a boolean value by looking at the first byte from Data
|
|
||||||
template <> inline bool assignFromBuf<bool>(const std::uint8_t* Data) {
|
|
||||||
return !!Data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace fmt_fuzzer
|
|
||||||
|
|
||||||
#endif // FUZZER_COMMON_H
|
|
22
test/fuzzing/main.cc
Normal file
22
test/fuzzing/main.cc
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#include <cassert>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fuzzer-common.h"
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::ifstream in(argv[i]);
|
||||||
|
assert(in);
|
||||||
|
in.seekg(0, std::ios_base::end);
|
||||||
|
const auto size = in.tellg();
|
||||||
|
assert(size >= 0);
|
||||||
|
in.seekg(0, std::ios_base::beg);
|
||||||
|
std::vector<char> buf(static_cast<size_t>(size));
|
||||||
|
in.read(buf.data(), size);
|
||||||
|
assert(in.gcount() == size);
|
||||||
|
LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
#include <cassert>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include "fuzzer_common.h"
|
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size);
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
std::ifstream in(argv[i]);
|
|
||||||
assert(in);
|
|
||||||
in.seekg(0, std::ios_base::end);
|
|
||||||
const auto pos = in.tellg();
|
|
||||||
assert(pos >= 0);
|
|
||||||
in.seekg(0, std::ios_base::beg);
|
|
||||||
std::vector<char> buf(static_cast<size_t>(pos));
|
|
||||||
in.read(buf.data(), static_cast<long>(buf.size()));
|
|
||||||
assert(in.gcount() == pos);
|
|
||||||
LLVMFuzzerTestOneInput(fmt_fuzzer::as_bytes(buf.data()), buf.size());
|
|
||||||
}
|
|
||||||
}
|
|
100
test/fuzzing/named-arg.cc
Normal file
100
test/fuzzing/named-arg.cc
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright (c) 2019, Paul Dreik
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
#include "fuzzer-common.h"
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
|
||||||
|
static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
|
||||||
|
if (size <= fixed_size) return;
|
||||||
|
const T value = assign_from_buf<T>(data);
|
||||||
|
data += fixed_size;
|
||||||
|
size -= fixed_size;
|
||||||
|
|
||||||
|
if (arg_name_size <= 0 || arg_name_size >= size) return;
|
||||||
|
data_to_string arg_name(data, arg_name_size, true);
|
||||||
|
data += arg_name_size;
|
||||||
|
size -= arg_name_size;
|
||||||
|
|
||||||
|
data_to_string format_str(data, size);
|
||||||
|
try {
|
||||||
|
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||||
|
std::string message =
|
||||||
|
fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
|
||||||
|
#else
|
||||||
|
fmt::memory_buffer out;
|
||||||
|
fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value));
|
||||||
|
#endif
|
||||||
|
} catch (std::exception&) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For dynamic dispatching to an explicit instantiation.
|
||||||
|
template <typename Callback> void invoke(int type, Callback callback) {
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
callback(bool());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
callback(char());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
using sc = signed char;
|
||||||
|
callback(sc());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
using uc = unsigned char;
|
||||||
|
callback(uc());
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
callback(short());
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
using us = unsigned short;
|
||||||
|
callback(us());
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
callback(int());
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
callback(unsigned());
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
callback(long());
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
using ul = unsigned long;
|
||||||
|
callback(ul());
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
callback(float());
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
callback(double());
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
using LD = long double;
|
||||||
|
callback(LD());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
if (size <= 3) return 0;
|
||||||
|
|
||||||
|
// Switch types depending on the first byte of the input.
|
||||||
|
const auto type = data[0] & 0x0F;
|
||||||
|
const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
|
||||||
|
data++;
|
||||||
|
size--;
|
||||||
|
|
||||||
|
invoke(type, [=](auto arg) {
|
||||||
|
invoke_fmt<decltype(arg)>(data, size, arg_name_size);
|
||||||
|
});
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,128 +0,0 @@
|
||||||
// Copyright (c) 2019, Paul Dreik
|
|
||||||
// License: see LICENSE.rst in the fmt root directory
|
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
#include "fuzzer_common.h"
|
|
||||||
|
|
||||||
template <typename Item1>
|
|
||||||
void invoke_fmt(const uint8_t* Data, size_t Size, unsigned int argsize) {
|
|
||||||
constexpr auto N1 = sizeof(Item1);
|
|
||||||
static_assert(N1 <= fmt_fuzzer::Nfixed, "Nfixed too small");
|
|
||||||
if (Size <= fmt_fuzzer::Nfixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
|
|
||||||
|
|
||||||
Data += fmt_fuzzer::Nfixed;
|
|
||||||
Size -= fmt_fuzzer::Nfixed;
|
|
||||||
|
|
||||||
// how many chars should be used for the argument name?
|
|
||||||
if (argsize <= 0 || argsize >= Size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocating buffers separately is slower, but increases chances
|
|
||||||
// of detecting memory errors
|
|
||||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
|
||||||
std::vector<char> argnamebuffer(argsize + 1);
|
|
||||||
std::memcpy(argnamebuffer.data(), Data, argsize);
|
|
||||||
auto argname = argnamebuffer.data();
|
|
||||||
#else
|
|
||||||
auto argname = fmt_fuzzer::as_chars(Data);
|
|
||||||
#endif
|
|
||||||
Data += argsize;
|
|
||||||
Size -= argsize;
|
|
||||||
|
|
||||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
|
||||||
// allocates as tight as possible, making it easier to catch buffer overruns.
|
|
||||||
std::vector<char> fmtstringbuffer(Size);
|
|
||||||
std::memcpy(fmtstringbuffer.data(), Data, Size);
|
|
||||||
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
|
|
||||||
#else
|
|
||||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
|
||||||
std::string message = fmt::format(fmtstring, fmt::arg(argname, item1));
|
|
||||||
#else
|
|
||||||
fmt::memory_buffer outbuf;
|
|
||||||
fmt::format_to(outbuf, fmtstring, fmt::arg(argname, item1));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// for dynamic dispatching to an explicit instantiation
|
|
||||||
template <typename Callback> void invoke(int index, Callback callback) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
callback(bool{});
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
callback(char{});
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
using sc = signed char;
|
|
||||||
callback(sc{});
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
using uc = unsigned char;
|
|
||||||
callback(uc{});
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
callback(short{});
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
using us = unsigned short;
|
|
||||||
callback(us{});
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
callback(int{});
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
callback(unsigned{});
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
callback(long{});
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
using ul = unsigned long;
|
|
||||||
callback(ul{});
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
callback(float{});
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
callback(double{});
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
using LD = long double;
|
|
||||||
callback(LD{});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
|
||||||
if (Size <= 3) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// switch types depending on the first byte of the input
|
|
||||||
const auto first = Data[0] & 0x0F;
|
|
||||||
const unsigned int second = (Data[0] & 0xF0) >> 4;
|
|
||||||
Data++;
|
|
||||||
Size--;
|
|
||||||
|
|
||||||
auto outerfcn = [=](auto param1) {
|
|
||||||
invoke_fmt<decltype(param1)>(Data, Size, second);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
invoke(first, outerfcn);
|
|
||||||
} catch (std::exception& /*e*/) {
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
91
test/fuzzing/one-arg.cc
Normal file
91
test/fuzzing/one-arg.cc
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright (c) 2019, Paul Dreik
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
#include "fuzzer-common.h"
|
||||||
|
|
||||||
|
template <typename T, typename Repr>
|
||||||
|
const T* from_repr(const Repr& r) { return &r; }
|
||||||
|
|
||||||
|
template <>
|
||||||
|
const std::tm* from_repr<std::tm>(const std::time_t& t) {
|
||||||
|
return std::localtime(&t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Repr = T>
|
||||||
|
void invoke_fmt(const uint8_t* data, size_t size) {
|
||||||
|
static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small");
|
||||||
|
if (size <= fixed_size) return;
|
||||||
|
auto repr = assign_from_buf<Repr>(data);
|
||||||
|
const T* value = from_repr<T>(repr);
|
||||||
|
if (!value) return;
|
||||||
|
data += fixed_size;
|
||||||
|
size -= fixed_size;
|
||||||
|
data_to_string format_str(data, size);
|
||||||
|
try {
|
||||||
|
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||||
|
std::string message = fmt::format(format_str.get(), *value);
|
||||||
|
#else
|
||||||
|
fmt::memory_buffer message;
|
||||||
|
fmt::format_to(message, format_str.get(), *value);
|
||||||
|
#endif
|
||||||
|
} catch (std::exception&) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
if (size <= 3) return 0;
|
||||||
|
|
||||||
|
const auto first = data[0];
|
||||||
|
data++;
|
||||||
|
size--;
|
||||||
|
|
||||||
|
switch (first) {
|
||||||
|
case 0:
|
||||||
|
invoke_fmt<bool>(data, size);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
invoke_fmt<char>(data, size);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
invoke_fmt<unsigned char>(data, size);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
invoke_fmt<signed char>(data, size);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
invoke_fmt<short>(data, size);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
invoke_fmt<unsigned short>(data, size);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
invoke_fmt<int>(data, size);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
invoke_fmt<unsigned int>(data, size);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
invoke_fmt<long>(data, size);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
invoke_fmt<unsigned long>(data, size);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
invoke_fmt<float>(data, size);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
invoke_fmt<double>(data, size);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
invoke_fmt<long double>(data, size);
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
invoke_fmt<std::tm, std::time_t>(data, size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,131 +0,0 @@
|
||||||
// Copyright (c) 2019, Paul Dreik
|
|
||||||
// License: see LICENSE.rst in the fmt root directory
|
|
||||||
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
|
||||||
#include "fuzzer_common.h"
|
|
||||||
|
|
||||||
using fmt_fuzzer::Nfixed;
|
|
||||||
|
|
||||||
template <typename Item>
|
|
||||||
void invoke_fmt(const uint8_t* Data, size_t Size) {
|
|
||||||
constexpr auto N = sizeof(Item);
|
|
||||||
static_assert(N <= Nfixed, "Nfixed is too small");
|
|
||||||
if (Size <= Nfixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
|
|
||||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
|
||||||
// allocates as tight as possible, making it easier to catch buffer overruns.
|
|
||||||
std::vector<char> fmtstringbuffer(Size);
|
|
||||||
std::memcpy(fmtstringbuffer.data(), Data, Size);
|
|
||||||
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
|
|
||||||
#else
|
|
||||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
|
||||||
std::string message = fmt::format(fmtstring, item);
|
|
||||||
#else
|
|
||||||
fmt::memory_buffer message;
|
|
||||||
fmt::format_to(message, fmtstring, item);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void invoke_fmt_time(const uint8_t* Data, size_t Size) {
|
|
||||||
using Item = std::time_t;
|
|
||||||
constexpr auto N = sizeof(Item);
|
|
||||||
static_assert(N <= Nfixed, "Nfixed too small");
|
|
||||||
if (Size <= Nfixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
#if FMT_FUZZ_SEPARATE_ALLOCATION
|
|
||||||
// allocates as tight as possible, making it easier to catch buffer overruns.
|
|
||||||
std::vector<char> fmtstringbuffer(Size);
|
|
||||||
std::memcpy(fmtstringbuffer.data(), Data, Size);
|
|
||||||
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
|
|
||||||
#else
|
|
||||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
|
||||||
#endif
|
|
||||||
auto* b = std::localtime(&item);
|
|
||||||
if (b) {
|
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
|
||||||
std::string message = fmt::format(fmtstring, *b);
|
|
||||||
#else
|
|
||||||
fmt::memory_buffer message;
|
|
||||||
fmt::format_to(message, fmtstring, *b);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
|
||||||
if (Size <= 3) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto first = Data[0];
|
|
||||||
Data++;
|
|
||||||
Size--;
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (first) {
|
|
||||||
case 0:
|
|
||||||
invoke_fmt<bool>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
invoke_fmt<char>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
invoke_fmt<unsigned char>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
invoke_fmt<signed char>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
invoke_fmt<short>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
invoke_fmt<unsigned short>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
invoke_fmt<int>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
invoke_fmt<unsigned int>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
invoke_fmt<long>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
invoke_fmt<unsigned long>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
invoke_fmt<float>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
invoke_fmt<double>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
invoke_fmt<long double>(Data, Size);
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
invoke_fmt_time(Data, Size);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (std::exception& /*e*/) {
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
// Copyright (c) 2019, Paul Dreik
|
|
||||||
// License: see LICENSE.rst in the fmt root directory
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <fmt/printf.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include "fuzzer_common.h"
|
|
||||||
|
|
||||||
using fmt_fuzzer::Nfixed;
|
|
||||||
|
|
||||||
template <typename Item1, typename Item2>
|
|
||||||
void invoke_fmt(const uint8_t* Data, size_t Size) {
|
|
||||||
constexpr auto N1 = sizeof(Item1);
|
|
||||||
constexpr auto N2 = sizeof(Item2);
|
|
||||||
static_assert(N1 <= Nfixed, "size1 exceeded");
|
|
||||||
static_assert(N2 <= Nfixed, "size2 exceeded");
|
|
||||||
if (Size <= Nfixed + Nfixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
|
|
||||||
Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data);
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
|
|
||||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
|
||||||
|
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
|
||||||
std::string message = fmt::format(fmtstring, item1, item2);
|
|
||||||
#else
|
|
||||||
fmt::memory_buffer message;
|
|
||||||
fmt::format_to(message, fmtstring, item1, item2);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// for dynamic dispatching to an explicit instantiation
|
|
||||||
template <typename Callback> void invoke(int index, Callback callback) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
callback(bool{});
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
callback(char{});
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
using sc = signed char;
|
|
||||||
callback(sc{});
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
using uc = unsigned char;
|
|
||||||
callback(uc{});
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
callback(short{});
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
using us = unsigned short;
|
|
||||||
callback(us{});
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
callback(int{});
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
callback(unsigned{});
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
callback(long{});
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
using ul = unsigned long;
|
|
||||||
callback(ul{});
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
callback(float{});
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
callback(double{});
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
using LD = long double;
|
|
||||||
callback(LD{});
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
using ptr = void*;
|
|
||||||
callback(ptr{});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
|
||||||
if (Size <= 3) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// switch types depending on the first byte of the input
|
|
||||||
const auto first = Data[0] & 0x0F;
|
|
||||||
const auto second = (Data[0] & 0xF0) >> 4;
|
|
||||||
Data++;
|
|
||||||
Size--;
|
|
||||||
|
|
||||||
auto outer = [=](auto param1) {
|
|
||||||
auto inner = [=](auto param2) {
|
|
||||||
invoke_fmt<decltype(param1), decltype(param2)>(Data, Size);
|
|
||||||
};
|
|
||||||
invoke(second, inner);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
invoke(first, outer);
|
|
||||||
} catch (std::exception& /*e*/) {
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
105
test/fuzzing/two-args.cc
Normal file
105
test/fuzzing/two-args.cc
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright (c) 2019, Paul Dreik
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <string>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "fuzzer-common.h"
|
||||||
|
|
||||||
|
template <typename Item1, typename Item2>
|
||||||
|
void invoke_fmt(const uint8_t* data, size_t size) {
|
||||||
|
static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded");
|
||||||
|
static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded");
|
||||||
|
if (size <= fixed_size + fixed_size) return;
|
||||||
|
|
||||||
|
const Item1 item1 = assign_from_buf<Item1>(data);
|
||||||
|
data += fixed_size;
|
||||||
|
size -= fixed_size;
|
||||||
|
|
||||||
|
const Item2 item2 = assign_from_buf<Item2>(data);
|
||||||
|
data += fixed_size;
|
||||||
|
size -= fixed_size;
|
||||||
|
|
||||||
|
auto format_str = fmt::string_view(as_chars(data), size);
|
||||||
|
#if FMT_FUZZ_FORMAT_TO_STRING
|
||||||
|
std::string message = fmt::format(format_str, item1, item2);
|
||||||
|
#else
|
||||||
|
fmt::memory_buffer message;
|
||||||
|
fmt::format_to(message, format_str, item1, item2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// For dynamic dispatching to an explicit instantiation.
|
||||||
|
template <typename Callback> void invoke(int index, Callback callback) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
callback(bool());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
callback(char());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
using sc = signed char;
|
||||||
|
callback(sc());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
using uc = unsigned char;
|
||||||
|
callback(uc());
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
callback(short());
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
using us = unsigned short;
|
||||||
|
callback(us());
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
callback(int());
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
callback(unsigned());
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
callback(long());
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
using ul = unsigned long;
|
||||||
|
callback(ul());
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
callback(float());
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
callback(double());
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
using LD = long double;
|
||||||
|
callback(LD());
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
using ptr = void*;
|
||||||
|
callback(ptr());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
if (size <= 3) return 0;
|
||||||
|
|
||||||
|
// Switch types depending on the first byte of the input.
|
||||||
|
const auto type1 = data[0] & 0x0F;
|
||||||
|
const auto type2 = (data[0] & 0xF0) >> 4;
|
||||||
|
data++;
|
||||||
|
size--;
|
||||||
|
try {
|
||||||
|
invoke(type1, [=](auto param1) {
|
||||||
|
invoke(type2, [=](auto param2) {
|
||||||
|
invoke_fmt<decltype(param1), decltype(param2)>(data, size);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (std::exception&) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,112 +0,0 @@
|
||||||
// Copyright (c) 2019, Paul Dreik
|
|
||||||
// License: see LICENSE.rst in the fmt root directory
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include "fuzzer_common.h"
|
|
||||||
|
|
||||||
constexpr auto Nfixed = fmt_fuzzer::Nfixed;
|
|
||||||
|
|
||||||
template <typename Item1, typename Item2>
|
|
||||||
void invoke_fmt(const uint8_t* Data, size_t Size) {
|
|
||||||
constexpr auto N1 = sizeof(Item1);
|
|
||||||
constexpr auto N2 = sizeof(Item2);
|
|
||||||
static_assert(N1 <= Nfixed, "size1 exceeded");
|
|
||||||
static_assert(N2 <= Nfixed, "size2 exceeded");
|
|
||||||
if (Size <= Nfixed + Nfixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
|
|
||||||
const Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data);
|
|
||||||
Data += Nfixed;
|
|
||||||
Size -= Nfixed;
|
|
||||||
|
|
||||||
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
|
|
||||||
|
|
||||||
#if FMT_FUZZ_FORMAT_TO_STRING
|
|
||||||
std::string message = fmt::format(fmtstring, item1, item2);
|
|
||||||
#else
|
|
||||||
fmt::memory_buffer message;
|
|
||||||
fmt::format_to(message, fmtstring, item1, item2);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// for dynamic dispatching to an explicit instantiation
|
|
||||||
template <typename Callback> void invoke(int index, Callback callback) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
callback(bool{});
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
callback(char{});
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
using sc = signed char;
|
|
||||||
callback(sc{});
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
using uc = unsigned char;
|
|
||||||
callback(uc{});
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
callback(short{});
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
using us = unsigned short;
|
|
||||||
callback(us{});
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
callback(int{});
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
callback(unsigned{});
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
callback(long{});
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
using ul = unsigned long;
|
|
||||||
callback(ul{});
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
callback(float{});
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
callback(double{});
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
using LD = long double;
|
|
||||||
callback(LD{});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
|
|
||||||
if (Size <= 3) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// switch types depending on the first byte of the input
|
|
||||||
const auto first = Data[0] & 0x0F;
|
|
||||||
const auto second = (Data[0] & 0xF0) >> 4;
|
|
||||||
Data++;
|
|
||||||
Size--;
|
|
||||||
|
|
||||||
auto outer = [=](auto param1) {
|
|
||||||
auto inner = [=](auto param2) {
|
|
||||||
invoke_fmt<decltype(param1), decltype(param2)>(Data, Size);
|
|
||||||
};
|
|
||||||
invoke(second, inner);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
invoke(first, outer);
|
|
||||||
} catch (std::exception& /*e*/) {
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
14204
test/gmock/gmock.h
14204
test/gmock/gmock.h
File diff suppressed because it is too large
Load diff
|
@ -1,75 +0,0 @@
|
||||||
// Formatting library for C++ - Grisu tests
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// For the license information refer to format.h.
|
|
||||||
|
|
||||||
#include "fmt/format.h"
|
|
||||||
#include "gtest.h"
|
|
||||||
|
|
||||||
static bool reported_skipped;
|
|
||||||
|
|
||||||
#undef TEST
|
|
||||||
#define TEST(test_fixture, test_name) \
|
|
||||||
void test_fixture##test_name(); \
|
|
||||||
GTEST_TEST(test_fixture, test_name) { \
|
|
||||||
if (FMT_USE_GRISU) { \
|
|
||||||
test_fixture##test_name(); \
|
|
||||||
} else if (!reported_skipped) { \
|
|
||||||
reported_skipped = true; \
|
|
||||||
fmt::print("Skipping Grisu tests.\n"); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
void test_fixture##test_name()
|
|
||||||
|
|
||||||
TEST(GrisuTest, NaN) {
|
|
||||||
auto nan = std::numeric_limits<double>::quiet_NaN();
|
|
||||||
EXPECT_EQ("nan", fmt::format("{}", nan));
|
|
||||||
EXPECT_EQ("-nan", fmt::format("{}", -nan));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GrisuTest, Inf) {
|
|
||||||
auto inf = std::numeric_limits<double>::infinity();
|
|
||||||
EXPECT_EQ("inf", fmt::format("{}", inf));
|
|
||||||
EXPECT_EQ("-inf", fmt::format("{}", -inf));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GrisuTest, Zero) { EXPECT_EQ("0.0", fmt::format("{}", 0.0)); }
|
|
||||||
|
|
||||||
TEST(GrisuTest, Round) {
|
|
||||||
EXPECT_EQ("1.9156918820264798e-56",
|
|
||||||
fmt::format("{}", 1.9156918820264798e-56));
|
|
||||||
EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GrisuTest, Prettify) {
|
|
||||||
EXPECT_EQ("0.0001", fmt::format("{}", 1e-4));
|
|
||||||
EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
|
|
||||||
EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
|
|
||||||
EXPECT_EQ("10000000000.0", fmt::format("{}", 1e10));
|
|
||||||
EXPECT_EQ("100000000000.0", fmt::format("{}", 1e11));
|
|
||||||
EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7));
|
|
||||||
EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
|
|
||||||
EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
|
|
||||||
EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
|
|
||||||
EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
|
|
||||||
|
|
||||||
TEST(GrisuTest, Fallback) {
|
|
||||||
EXPECT_EQ("1e+23", fmt::format("{}", 1e23));
|
|
||||||
EXPECT_EQ("9e-265", fmt::format("{}", 9e-265));
|
|
||||||
EXPECT_EQ("5.423717798060526e+125",
|
|
||||||
fmt::format("{}", 5.423717798060526e+125));
|
|
||||||
EXPECT_EQ("1.372371880954233e-288",
|
|
||||||
fmt::format("{}", 1.372371880954233e-288));
|
|
||||||
EXPECT_EQ("55388492.622190244", fmt::format("{}", 55388492.622190244));
|
|
||||||
EXPECT_EQ("2.2506787569811123e-253",
|
|
||||||
fmt::format("{}", 2.2506787569811123e-253));
|
|
||||||
EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8));
|
|
||||||
// pow(2, -25) - assymetric boundaries:
|
|
||||||
EXPECT_EQ("2.9802322387695312e-08",
|
|
||||||
fmt::format("{}", 2.9802322387695312e-08));
|
|
||||||
}
|
|
|
@ -9,31 +9,18 @@
|
||||||
|
|
||||||
#include <gtest/gtest-spi.h>
|
#include <gtest/gtest-spi.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
#include "fmt/os.h"
|
||||||
# include <crtdbg.h> // for _CrtSetReportMode
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// This is used to suppress coverity warnings about untrusted values.
|
|
||||||
std::string sanitize(const std::string& s) {
|
|
||||||
std::string result;
|
|
||||||
for (std::string::const_iterator i = s.begin(), end = s.end(); i != end; ++i)
|
|
||||||
result.push_back(static_cast<char>(*i & 0xff));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that assertion macros evaluate their arguments exactly once.
|
// Tests that assertion macros evaluate their arguments exactly once.
|
||||||
class SingleEvaluationTest : public ::testing::Test {
|
namespace {
|
||||||
|
class single_evaluation_test : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
SingleEvaluationTest() {
|
single_evaluation_test() {
|
||||||
p_ = s_;
|
p_ = s_;
|
||||||
a_ = 0;
|
a_ = 0;
|
||||||
b_ = 0;
|
b_ = 0;
|
||||||
|
@ -45,11 +32,12 @@ class SingleEvaluationTest : public ::testing::Test {
|
||||||
static int a_;
|
static int a_;
|
||||||
static int b_;
|
static int b_;
|
||||||
};
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
const char* const SingleEvaluationTest::s_ = "01234";
|
const char* const single_evaluation_test::s_ = "01234";
|
||||||
const char* SingleEvaluationTest::p_;
|
const char* single_evaluation_test::p_;
|
||||||
int SingleEvaluationTest::a_;
|
int single_evaluation_test::a_;
|
||||||
int SingleEvaluationTest::b_;
|
int single_evaluation_test::b_;
|
||||||
|
|
||||||
void do_nothing() {}
|
void do_nothing() {}
|
||||||
|
|
||||||
|
@ -61,7 +49,7 @@ FMT_NORETURN void throw_system_error() {
|
||||||
|
|
||||||
// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument
|
// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument
|
||||||
// exactly once.
|
// exactly once.
|
||||||
TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) {
|
TEST_F(single_evaluation_test, failed_expect_throw_msg) {
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234");
|
EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234");
|
||||||
EXPECT_EQ(s_ + 1, p_);
|
EXPECT_EQ(s_ + 1, p_);
|
||||||
|
@ -69,14 +57,14 @@ TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) {
|
||||||
|
|
||||||
// Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument
|
// Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument
|
||||||
// exactly once.
|
// exactly once.
|
||||||
TEST_F(SingleEvaluationTest, FailedEXPECT_SYSTEM_ERROR) {
|
TEST_F(single_evaluation_test, failed_expect_system_error) {
|
||||||
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++),
|
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++),
|
||||||
"01234");
|
"01234");
|
||||||
EXPECT_EQ(s_ + 1, p_);
|
EXPECT_EQ(s_ + 1, p_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that assertion arguments are evaluated exactly once.
|
// Tests that assertion arguments are evaluated exactly once.
|
||||||
TEST_F(SingleEvaluationTest, ExceptionTests) {
|
TEST_F(single_evaluation_test, exception_tests) {
|
||||||
// successful EXPECT_THROW_MSG
|
// successful EXPECT_THROW_MSG
|
||||||
EXPECT_THROW_MSG(
|
EXPECT_THROW_MSG(
|
||||||
{ // NOLINT
|
{ // NOLINT
|
||||||
|
@ -116,7 +104,7 @@ TEST_F(SingleEvaluationTest, ExceptionTests) {
|
||||||
EXPECT_EQ(4, b_);
|
EXPECT_EQ(4, b_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SingleEvaluationTest, SystemErrorTests) {
|
TEST_F(single_evaluation_test, system_error_tests) {
|
||||||
// successful EXPECT_SYSTEM_ERROR
|
// successful EXPECT_SYSTEM_ERROR
|
||||||
EXPECT_SYSTEM_ERROR(
|
EXPECT_SYSTEM_ERROR(
|
||||||
{ // NOLINT
|
{ // NOLINT
|
||||||
|
@ -159,14 +147,14 @@ TEST_F(SingleEvaluationTest, SystemErrorTests) {
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
// Tests that when EXPECT_WRITE fails, it evaluates its message argument
|
// Tests that when EXPECT_WRITE fails, it evaluates its message argument
|
||||||
// exactly once.
|
// exactly once.
|
||||||
TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
|
TEST_F(single_evaluation_test, failed_expect_write) {
|
||||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
|
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
|
||||||
"01234");
|
"01234");
|
||||||
EXPECT_EQ(s_ + 1, p_);
|
EXPECT_EQ(s_ + 1, p_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that assertion arguments are evaluated exactly once.
|
// Tests that assertion arguments are evaluated exactly once.
|
||||||
TEST_F(SingleEvaluationTest, WriteTests) {
|
TEST_F(single_evaluation_test, write_tests) {
|
||||||
// successful EXPECT_WRITE
|
// successful EXPECT_WRITE
|
||||||
EXPECT_WRITE(
|
EXPECT_WRITE(
|
||||||
stdout,
|
stdout,
|
||||||
|
@ -192,7 +180,7 @@ TEST_F(SingleEvaluationTest, WriteTests) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests EXPECT_WRITE.
|
// Tests EXPECT_WRITE.
|
||||||
TEST(ExpectTest, EXPECT_WRITE) {
|
TEST(gtest_extra_test, expect_write) {
|
||||||
EXPECT_WRITE(stdout, do_nothing(), "");
|
EXPECT_WRITE(stdout, do_nothing(), "");
|
||||||
EXPECT_WRITE(stdout, std::printf("test"), "test");
|
EXPECT_WRITE(stdout, std::printf("test"), "test");
|
||||||
EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
|
EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
|
||||||
|
@ -201,7 +189,7 @@ TEST(ExpectTest, EXPECT_WRITE) {
|
||||||
" Actual: that");
|
" Actual: that");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
TEST(gtest_extra_test, expect_write_streaming) {
|
||||||
EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
|
EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
|
||||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
|
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
|
||||||
<< "expected failure",
|
<< "expected failure",
|
||||||
|
@ -211,7 +199,7 @@ TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||||
|
|
||||||
// Tests that the compiler will not complain about unreachable code in the
|
// Tests that the compiler will not complain about unreachable code in the
|
||||||
// EXPECT_THROW_MSG macro.
|
// EXPECT_THROW_MSG macro.
|
||||||
TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
|
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
using std::runtime_error;
|
using std::runtime_error;
|
||||||
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
|
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
|
||||||
|
@ -223,7 +211,7 @@ TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||||
|
|
||||||
// Tests that the compiler will not complain about unreachable code in the
|
// Tests that the compiler will not complain about unreachable code in the
|
||||||
// EXPECT_SYSTEM_ERROR macro.
|
// EXPECT_SYSTEM_ERROR macro.
|
||||||
TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
|
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
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, ""), "");
|
||||||
|
@ -233,7 +221,7 @@ TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
|
TEST(gtest_extra_test, expect_throw_behaves_like_single_statement) {
|
||||||
if (::testing::internal::AlwaysFalse())
|
if (::testing::internal::AlwaysFalse())
|
||||||
EXPECT_THROW_MSG(do_nothing(), std::exception, "");
|
EXPECT_THROW_MSG(do_nothing(), std::exception, "");
|
||||||
|
|
||||||
|
@ -243,7 +231,7 @@ TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
|
||||||
do_nothing();
|
do_nothing();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) {
|
TEST(gtest_extra_test, expect_system_error_behaves_like_single_statement) {
|
||||||
if (::testing::internal::AlwaysFalse())
|
if (::testing::internal::AlwaysFalse())
|
||||||
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "");
|
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "");
|
||||||
|
|
||||||
|
@ -253,7 +241,7 @@ TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) {
|
||||||
do_nothing();
|
do_nothing();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) {
|
TEST(gtest_extra_test, expect_write_behaves_like_single_statement) {
|
||||||
if (::testing::internal::AlwaysFalse())
|
if (::testing::internal::AlwaysFalse())
|
||||||
EXPECT_WRITE(stdout, std::printf("x"), "x");
|
EXPECT_WRITE(stdout, std::printf("x"), "x");
|
||||||
|
|
||||||
|
@ -264,7 +252,7 @@ TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests EXPECT_THROW_MSG.
|
// Tests EXPECT_THROW_MSG.
|
||||||
TEST(ExpectTest, EXPECT_THROW_MSG) {
|
TEST(gtest_extra_test, expect_throw_msg) {
|
||||||
EXPECT_THROW_MSG(throw_exception(), std::exception, "test");
|
EXPECT_THROW_MSG(throw_exception(), std::exception, "test");
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"),
|
EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"),
|
||||||
|
@ -282,15 +270,15 @@ TEST(ExpectTest, EXPECT_THROW_MSG) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests EXPECT_SYSTEM_ERROR.
|
// Tests EXPECT_SYSTEM_ERROR.
|
||||||
TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
|
TEST(gtest_extra_test, expect_system_error) {
|
||||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test");
|
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test");
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"),
|
EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"),
|
||||||
"Expected: throw_exception() throws an exception of "
|
"Expected: throw_exception() throws an exception of "
|
||||||
"type fmt::system_error.\n Actual: it throws a different type.");
|
"type std::system_error.\n Actual: it throws a different type.");
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"),
|
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"),
|
||||||
"Expected: do_nothing() throws an exception of type fmt::system_error.\n"
|
"Expected: do_nothing() throws an exception of type std::system_error.\n"
|
||||||
" Actual: it throws nothing.");
|
" Actual: it throws nothing.");
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"),
|
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"),
|
||||||
|
@ -298,11 +286,11 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
|
||||||
"throw_system_error() throws an exception with a different message.\n"
|
"throw_system_error() throws an exception with a different message.\n"
|
||||||
"Expected: {}\n"
|
"Expected: {}\n"
|
||||||
" Actual: {}",
|
" Actual: {}",
|
||||||
format_system_error(EDOM, "other"),
|
system_error_message(EDOM, "other"),
|
||||||
format_system_error(EDOM, "test")));
|
system_error_message(EDOM, "test")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
|
TEST(gtest_extra_test, expect_throw_msg_streaming) {
|
||||||
EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
|
EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
|
||||||
<< "unexpected failure";
|
<< "unexpected failure";
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
|
@ -311,7 +299,7 @@ TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
|
||||||
"expected failure");
|
"expected failure");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
|
TEST(gtest_extra_test, expect_system_error_streaming) {
|
||||||
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test")
|
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test")
|
||||||
<< "unexpected failure";
|
<< "unexpected failure";
|
||||||
EXPECT_NONFATAL_FAILURE(
|
EXPECT_NONFATAL_FAILURE(
|
||||||
|
@ -320,31 +308,19 @@ TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
|
||||||
"expected failure");
|
"expected failure");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, FormatSystemError) {
|
|
||||||
fmt::memory_buffer out;
|
|
||||||
fmt::format_system_error(out, EDOM, "test message");
|
|
||||||
EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
|
|
||||||
using fmt::buffered_file;
|
using fmt::buffered_file;
|
||||||
using fmt::error_code;
|
|
||||||
using fmt::file;
|
using fmt::file;
|
||||||
|
|
||||||
TEST(ErrorCodeTest, Ctor) {
|
TEST(output_redirect_test, scoped_redirect) {
|
||||||
EXPECT_EQ(error_code().get(), 0);
|
|
||||||
EXPECT_EQ(error_code(42).get(), 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OutputRedirectTest, ScopedRedirect) {
|
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
{
|
{
|
||||||
buffered_file file(write_end.fdopen("w"));
|
buffered_file file(write_end.fdopen("w"));
|
||||||
std::fprintf(file.get(), "[[[");
|
std::fprintf(file.get(), "[[[");
|
||||||
{
|
{
|
||||||
OutputRedirect redir(file.get());
|
output_redirect redir(file.get());
|
||||||
std::fprintf(file.get(), "censored");
|
std::fprintf(file.get(), "censored");
|
||||||
}
|
}
|
||||||
std::fprintf(file.get(), "]]]");
|
std::fprintf(file.get(), "]]]");
|
||||||
|
@ -352,8 +328,8 @@ TEST(OutputRedirectTest, ScopedRedirect) {
|
||||||
EXPECT_READ(read_end, "[[[]]]");
|
EXPECT_READ(read_end, "[[[]]]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that OutputRedirect handles errors in flush correctly.
|
// Test that output_redirect handles errors in flush correctly.
|
||||||
TEST(OutputRedirectTest, FlushErrorInCtor) {
|
TEST(output_redirect_test, flush_error_in_ctor) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
int write_fd = write_end.descriptor();
|
int write_fd = write_end.descriptor();
|
||||||
|
@ -362,47 +338,47 @@ TEST(OutputRedirectTest, FlushErrorInCtor) {
|
||||||
// Put a character in a file buffer.
|
// Put a character in a file buffer.
|
||||||
EXPECT_EQ('x', fputc('x', f.get()));
|
EXPECT_EQ('x', fputc('x', f.get()));
|
||||||
FMT_POSIX(close(write_fd));
|
FMT_POSIX(close(write_fd));
|
||||||
std::unique_ptr<OutputRedirect> redir{nullptr};
|
std::unique_ptr<output_redirect> redir{nullptr};
|
||||||
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new OutputRedirect(f.get())), EBADF,
|
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new output_redirect(f.get())), EBADF,
|
||||||
"cannot flush stream");
|
"cannot flush stream");
|
||||||
redir.reset(nullptr);
|
redir.reset(nullptr);
|
||||||
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OutputRedirectTest, DupErrorInCtor) {
|
TEST(output_redirect_test, dup_error_in_ctor) {
|
||||||
buffered_file f = open_buffered_file();
|
buffered_file f = open_buffered_file();
|
||||||
int fd = (f.fileno)();
|
int fd = (f.fileno)();
|
||||||
file copy = file::dup(fd);
|
file copy = file::dup(fd);
|
||||||
FMT_POSIX(close(fd));
|
FMT_POSIX(close(fd));
|
||||||
std::unique_ptr<OutputRedirect> redir{nullptr};
|
std::unique_ptr<output_redirect> redir{nullptr};
|
||||||
EXPECT_SYSTEM_ERROR_NOASSERT(
|
EXPECT_SYSTEM_ERROR_NOASSERT(
|
||||||
redir.reset(new OutputRedirect(f.get())), EBADF,
|
redir.reset(new output_redirect(f.get())), EBADF,
|
||||||
fmt::format("cannot duplicate file descriptor {}", fd));
|
fmt::format("cannot duplicate file descriptor {}", fd));
|
||||||
copy.dup2(fd); // "undo" close or dtor will fail
|
copy.dup2(fd); // "undo" close or dtor will fail
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OutputRedirectTest, RestoreAndRead) {
|
TEST(output_redirect_test, restore_and_read) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
buffered_file file(write_end.fdopen("w"));
|
buffered_file file(write_end.fdopen("w"));
|
||||||
std::fprintf(file.get(), "[[[");
|
std::fprintf(file.get(), "[[[");
|
||||||
OutputRedirect redir(file.get());
|
output_redirect redir(file.get());
|
||||||
std::fprintf(file.get(), "censored");
|
std::fprintf(file.get(), "censored");
|
||||||
EXPECT_EQ("censored", sanitize(redir.restore_and_read()));
|
EXPECT_EQ("censored", redir.restore_and_read());
|
||||||
EXPECT_EQ("", sanitize(redir.restore_and_read()));
|
EXPECT_EQ("", redir.restore_and_read());
|
||||||
std::fprintf(file.get(), "]]]");
|
std::fprintf(file.get(), "]]]");
|
||||||
file = buffered_file();
|
file = buffered_file();
|
||||||
EXPECT_READ(read_end, "[[[]]]");
|
EXPECT_READ(read_end, "[[[]]]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that OutputRedirect handles errors in flush correctly.
|
// Test that OutputRedirect handles errors in flush correctly.
|
||||||
TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
|
TEST(output_redirect_test, flush_error_in_restore_and_read) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
int write_fd = write_end.descriptor();
|
int write_fd = write_end.descriptor();
|
||||||
file write_copy = write_end.dup(write_fd);
|
file write_copy = write_end.dup(write_fd);
|
||||||
buffered_file f = write_end.fdopen("w");
|
buffered_file f = write_end.fdopen("w");
|
||||||
OutputRedirect redir(f.get());
|
output_redirect redir(f.get());
|
||||||
// Put a character in a file buffer.
|
// Put a character in a file buffer.
|
||||||
EXPECT_EQ('x', fputc('x', f.get()));
|
EXPECT_EQ('x', fputc('x', f.get()));
|
||||||
FMT_POSIX(close(write_fd));
|
FMT_POSIX(close(write_fd));
|
||||||
|
@ -411,13 +387,13 @@ TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
|
||||||
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
write_copy.dup2(write_fd); // "undo" close or dtor will fail
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OutputRedirectTest, ErrorInDtor) {
|
TEST(output_redirect_test, error_in_dtor) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
int write_fd = write_end.descriptor();
|
int write_fd = write_end.descriptor();
|
||||||
file write_copy = write_end.dup(write_fd);
|
file write_copy = write_end.dup(write_fd);
|
||||||
buffered_file f = write_end.fdopen("w");
|
buffered_file f = write_end.fdopen("w");
|
||||||
std::unique_ptr<OutputRedirect> redir(new OutputRedirect(f.get()));
|
std::unique_ptr<output_redirect> redir(new output_redirect(f.get()));
|
||||||
// Put a character in a file buffer.
|
// Put a character in a file buffer.
|
||||||
EXPECT_EQ('x', fputc('x', f.get()));
|
EXPECT_EQ('x', fputc('x', f.get()));
|
||||||
EXPECT_WRITE(
|
EXPECT_WRITE(
|
||||||
|
@ -430,10 +406,8 @@ TEST(OutputRedirectTest, ErrorInDtor) {
|
||||||
FMT_POSIX(close(write_fd));
|
FMT_POSIX(close(write_fd));
|
||||||
SUPPRESS_ASSERT(redir.reset(nullptr));
|
SUPPRESS_ASSERT(redir.reset(nullptr));
|
||||||
},
|
},
|
||||||
format_system_error(EBADF, "cannot flush stream"));
|
system_error_message(EBADF, "cannot flush stream"));
|
||||||
write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail
|
write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
|
@ -11,24 +11,7 @@
|
||||||
|
|
||||||
using fmt::file;
|
using fmt::file;
|
||||||
|
|
||||||
void OutputRedirect::flush() {
|
output_redirect::output_redirect(FILE* f) : file_(f) {
|
||||||
# if EOF != -1
|
|
||||||
# error "FMT_RETRY assumes return value of -1 indicating failure"
|
|
||||||
# endif
|
|
||||||
int result = 0;
|
|
||||||
FMT_RETRY(result, fflush(file_));
|
|
||||||
if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
void OutputRedirect::restore() {
|
|
||||||
if (original_.descriptor() == -1) return; // Already restored.
|
|
||||||
flush();
|
|
||||||
// Restore the original file.
|
|
||||||
original_.dup2(FMT_POSIX(fileno(file_)));
|
|
||||||
original_.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputRedirect::OutputRedirect(FILE* f) : file_(f) {
|
|
||||||
flush();
|
flush();
|
||||||
int fd = FMT_POSIX(fileno(f));
|
int fd = FMT_POSIX(fileno(f));
|
||||||
// Create a file object referring to the original file.
|
// Create a file object referring to the original file.
|
||||||
|
@ -40,7 +23,7 @@ OutputRedirect::OutputRedirect(FILE* f) : file_(f) {
|
||||||
write_end.dup2(fd);
|
write_end.dup2(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputRedirect::~OutputRedirect() FMT_NOEXCEPT {
|
output_redirect::~output_redirect() FMT_NOEXCEPT {
|
||||||
try {
|
try {
|
||||||
restore();
|
restore();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
@ -48,7 +31,24 @@ OutputRedirect::~OutputRedirect() FMT_NOEXCEPT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OutputRedirect::restore_and_read() {
|
void output_redirect::flush() {
|
||||||
|
# if EOF != -1
|
||||||
|
# error "FMT_RETRY assumes return value of -1 indicating failure"
|
||||||
|
# endif
|
||||||
|
int result = 0;
|
||||||
|
FMT_RETRY(result, fflush(file_));
|
||||||
|
if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_redirect::restore() {
|
||||||
|
if (original_.descriptor() == -1) return; // Already restored.
|
||||||
|
flush();
|
||||||
|
// Restore the original file.
|
||||||
|
original_.dup2(FMT_POSIX(fileno(file_)));
|
||||||
|
original_.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string output_redirect::restore_and_read() {
|
||||||
// Restore output.
|
// Restore output.
|
||||||
restore();
|
restore();
|
||||||
|
|
||||||
|
@ -79,9 +79,3 @@ std::string read(file& f, size_t count) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
std::string format_system_error(int error_code, fmt::string_view message) {
|
|
||||||
fmt::memory_buffer out;
|
|
||||||
format_system_error(out, error_code, message);
|
|
||||||
return to_string(out);
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
#ifndef FMT_GTEST_EXTRA_H_
|
#ifndef FMT_GTEST_EXTRA_H_
|
||||||
#define FMT_GTEST_EXTRA_H_
|
#define FMT_GTEST_EXTRA_H_
|
||||||
|
|
||||||
|
#include <stdlib.h> // _invalid_parameter_handler
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "fmt/os.h"
|
#include "fmt/os.h"
|
||||||
#include "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) \
|
||||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||||
|
@ -51,30 +53,35 @@
|
||||||
FMT_TEST_THROW_(statement, expected_exception, expected_message, \
|
FMT_TEST_THROW_(statement, expected_exception, expected_message, \
|
||||||
GTEST_NONFATAL_FAILURE_)
|
GTEST_NONFATAL_FAILURE_)
|
||||||
|
|
||||||
std::string format_system_error(int error_code, fmt::string_view message);
|
inline std::string system_error_message(int error_code,
|
||||||
|
const std::string& message) {
|
||||||
|
auto ec = std::error_code(error_code, std::generic_category());
|
||||||
|
return std::system_error(ec, message).what();
|
||||||
|
}
|
||||||
|
|
||||||
#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \
|
#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \
|
||||||
EXPECT_THROW_MSG(statement, fmt::system_error, \
|
EXPECT_THROW_MSG(statement, std::system_error, \
|
||||||
format_system_error(error_code, message))
|
system_error_message(error_code, message))
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
|
|
||||||
// Captures file output by redirecting it to a pipe.
|
// Captures file output by redirecting it to a pipe.
|
||||||
// The output it can handle is limited by the pipe capacity.
|
// The output it can handle is limited by the pipe capacity.
|
||||||
class OutputRedirect {
|
class output_redirect {
|
||||||
private:
|
private:
|
||||||
FILE* file_;
|
FILE* file_;
|
||||||
fmt::file original_; // Original file passed to redirector.
|
fmt::file original_; // Original file passed to redirector.
|
||||||
fmt::file read_end_; // Read end of the pipe where the output is redirected.
|
fmt::file read_end_; // Read end of the pipe where the output is redirected.
|
||||||
|
|
||||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(OutputRedirect);
|
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
void restore();
|
void restore();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit OutputRedirect(FILE* file);
|
explicit output_redirect(FILE* file);
|
||||||
~OutputRedirect() FMT_NOEXCEPT;
|
~output_redirect() FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
output_redirect(const output_redirect&) = delete;
|
||||||
|
void operator=(const output_redirect&) = delete;
|
||||||
|
|
||||||
// Restores the original file, reads output from the pipe into a string
|
// Restores the original file, reads output from the pipe into a string
|
||||||
// and returns it.
|
// and returns it.
|
||||||
|
@ -85,7 +92,7 @@ class OutputRedirect {
|
||||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||||
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
|
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
|
||||||
std::string gtest_expected_output = expected_output; \
|
std::string gtest_expected_output = expected_output; \
|
||||||
OutputRedirect gtest_redir(file); \
|
output_redirect gtest_redir(file); \
|
||||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
||||||
std::string gtest_output = gtest_redir.restore_and_read(); \
|
std::string gtest_output = gtest_redir.restore_and_read(); \
|
||||||
if (gtest_output != gtest_expected_output) { \
|
if (gtest_output != gtest_expected_output) { \
|
||||||
|
@ -106,7 +113,7 @@ class OutputRedirect {
|
||||||
|
|
||||||
// Suppresses Windows assertions on invalid file descriptors, making
|
// Suppresses Windows assertions on invalid file descriptors, making
|
||||||
// POSIX functions return proper error codes instead of crashing on Windows.
|
// POSIX functions return proper error codes instead of crashing on Windows.
|
||||||
class SuppressAssert {
|
class suppress_assert {
|
||||||
private:
|
private:
|
||||||
_invalid_parameter_handler original_handler_;
|
_invalid_parameter_handler original_handler_;
|
||||||
int original_report_mode_;
|
int original_report_mode_;
|
||||||
|
@ -115,11 +122,11 @@ class SuppressAssert {
|
||||||
const wchar_t*, unsigned, uintptr_t) {}
|
const wchar_t*, unsigned, uintptr_t) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SuppressAssert()
|
suppress_assert()
|
||||||
: original_handler_(
|
: original_handler_(
|
||||||
_set_invalid_parameter_handler(handle_invalid_parameter)),
|
_set_invalid_parameter_handler(handle_invalid_parameter)),
|
||||||
original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {}
|
original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {}
|
||||||
~SuppressAssert() {
|
~suppress_assert() {
|
||||||
_set_invalid_parameter_handler(original_handler_);
|
_set_invalid_parameter_handler(original_handler_);
|
||||||
_CrtSetReportMode(_CRT_ASSERT, original_report_mode_);
|
_CrtSetReportMode(_CRT_ASSERT, original_report_mode_);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +134,7 @@ class SuppressAssert {
|
||||||
|
|
||||||
# define SUPPRESS_ASSERT(statement) \
|
# define SUPPRESS_ASSERT(statement) \
|
||||||
{ \
|
{ \
|
||||||
SuppressAssert sa; \
|
suppress_assert sa; \
|
||||||
statement; \
|
statement; \
|
||||||
}
|
}
|
||||||
# else
|
# else
|
||||||
|
@ -141,16 +148,17 @@ class SuppressAssert {
|
||||||
std::string read(fmt::file& f, size_t count);
|
std::string read(fmt::file& f, size_t count);
|
||||||
|
|
||||||
# define EXPECT_READ(file, expected_content) \
|
# define EXPECT_READ(file, expected_content) \
|
||||||
EXPECT_EQ(expected_content, \
|
EXPECT_EQ(expected_content, \
|
||||||
read(file, fmt::string_view(expected_content).size()))
|
read(file, fmt::string_view(expected_content).size()))
|
||||||
|
|
||||||
#else
|
#else
|
||||||
# define EXPECT_WRITE(file, statement, expected_output) SUCCEED()
|
# define EXPECT_WRITE(file, statement, expected_output) \
|
||||||
|
do { \
|
||||||
|
(void)(file); \
|
||||||
|
(void)(statement); \
|
||||||
|
(void)(expected_output); \
|
||||||
|
SUCCEED(); \
|
||||||
|
} while (false)
|
||||||
#endif // FMT_USE_FCNTL
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {
|
|
||||||
ScopedMock() { Mock::instance = this; }
|
|
||||||
~ScopedMock() { Mock::instance = nullptr; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // FMT_GTEST_EXTRA_H_
|
#endif // FMT_GTEST_EXTRA_H_
|
||||||
|
|
3
test/gtest/.clang-format
Normal file
3
test/gtest/.clang-format
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Disable clang-format here
|
||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: Never
|
31
test/gtest/CMakeLists.txt
Normal file
31
test/gtest/CMakeLists.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Build the google test library
|
||||||
|
|
||||||
|
# We compile Google Test ourselves instead of using pre-compiled libraries.
|
||||||
|
# See the Google Test FAQ "Why is it not recommended to install a
|
||||||
|
# pre-compiled copy of Google Test (for example, into /usr/local)?"
|
||||||
|
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
|
||||||
|
add_library(gtest STATIC
|
||||||
|
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_include_directories(gtest SYSTEM PUBLIC .)
|
||||||
|
|
||||||
|
find_package(Threads)
|
||||||
|
if (Threads_FOUND)
|
||||||
|
target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
else ()
|
||||||
|
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
|
||||||
|
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
# Disable MSVC warnings of POSIX functions.
|
||||||
|
target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Silence MSVC tr1 deprecation warning in gmock.
|
||||||
|
target_compile_definitions(gtest
|
||||||
|
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
|
File diff suppressed because it is too large
Load diff
11645
test/gtest/gmock/gmock.h
Normal file
11645
test/gtest/gmock/gmock.h
Normal file
File diff suppressed because it is too large
Load diff
20065
test/gtest/gtest.h
20065
test/gtest/gtest.h
File diff suppressed because it is too large
Load diff
|
@ -26,17 +26,21 @@
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
//
|
|
||||||
// Author: wan@google.com (Zhanyong Wan)
|
|
||||||
//
|
//
|
||||||
// Utilities for testing Google Test itself and code that uses Google Test
|
// Utilities for testing Google Test itself and code that uses Google Test
|
||||||
// (e.g. frameworks built on top of Google Test).
|
// (e.g. frameworks built on top of Google Test).
|
||||||
|
|
||||||
#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_
|
// GOOGLETEST_CM0004 DO NOT DELETE
|
||||||
#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_
|
|
||||||
|
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||||
|
#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||||
|
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||||
|
|
||||||
namespace testing {
|
namespace testing {
|
||||||
|
|
||||||
// This helper class can be used to mock out Google Test failure reporting
|
// This helper class can be used to mock out Google Test failure reporting
|
||||||
|
@ -68,14 +72,15 @@ class GTEST_API_ ScopedFakeTestPartResultReporter
|
||||||
TestPartResultArray* result);
|
TestPartResultArray* result);
|
||||||
|
|
||||||
// The d'tor restores the previous test part result reporter.
|
// The d'tor restores the previous test part result reporter.
|
||||||
virtual ~ScopedFakeTestPartResultReporter();
|
~ScopedFakeTestPartResultReporter() override;
|
||||||
|
|
||||||
// Appends the TestPartResult object to the TestPartResultArray
|
// Appends the TestPartResult object to the TestPartResultArray
|
||||||
// received in the constructor.
|
// received in the constructor.
|
||||||
//
|
//
|
||||||
// This method is from the TestPartResultReporterInterface
|
// This method is from the TestPartResultReporterInterface
|
||||||
// interface.
|
// interface.
|
||||||
virtual void ReportTestPartResult(const TestPartResult& result);
|
void ReportTestPartResult(const TestPartResult& result) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
|
@ -97,13 +102,12 @@ class GTEST_API_ SingleFailureChecker {
|
||||||
public:
|
public:
|
||||||
// The constructor remembers the arguments.
|
// The constructor remembers the arguments.
|
||||||
SingleFailureChecker(const TestPartResultArray* results,
|
SingleFailureChecker(const TestPartResultArray* results,
|
||||||
TestPartResult::Type type,
|
TestPartResult::Type type, const std::string& substr);
|
||||||
const string& substr);
|
|
||||||
~SingleFailureChecker();
|
~SingleFailureChecker();
|
||||||
private:
|
private:
|
||||||
const TestPartResultArray* const results_;
|
const TestPartResultArray* const results_;
|
||||||
const TestPartResult::Type type_;
|
const TestPartResult::Type type_;
|
||||||
const string substr_;
|
const std::string substr_;
|
||||||
|
|
||||||
GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
|
GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
|
||||||
};
|
};
|
||||||
|
@ -112,6 +116,8 @@ class GTEST_API_ SingleFailureChecker {
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
|
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||||
|
|
||||||
// A set of macros for testing Google Test assertions or code that's expected
|
// A set of macros for testing Google Test assertions or code that's expected
|
||||||
// to generate Google Test fatal failures. It verifies that the given
|
// to generate Google Test fatal failures. It verifies that the given
|
||||||
// statement will cause exactly one fatal Google Test failure with 'substr'
|
// statement will cause exactly one fatal Google Test failure with 'substr'
|
||||||
|
@ -229,4 +235,4 @@ class GTEST_API_ SingleFailureChecker {
|
||||||
}\
|
}\
|
||||||
} while (::testing::internal::AlwaysFalse())
|
} while (::testing::internal::AlwaysFalse())
|
||||||
|
|
||||||
#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_
|
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
12398
test/gtest/gtest/gtest.h
Normal file
12398
test/gtest/gtest/gtest.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,7 @@
|
||||||
// Header-only configuration test
|
// Header-only configuration test
|
||||||
|
|
||||||
#include "fmt/core.h"
|
#include "fmt/core.h"
|
||||||
|
|
||||||
|
#ifndef FMT_HEADER_ONLY
|
||||||
|
# error "Not in the header-only mode."
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Additional translation unit for the header-only configuration test
|
|
||||||
|
|
||||||
#include "fmt/core.h"
|
|
|
@ -1,109 +0,0 @@
|
||||||
// Formatting library for C++ - locale tests
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// For the license information refer to format.h.
|
|
||||||
|
|
||||||
#include "fmt/locale.h"
|
|
||||||
|
|
||||||
#include "gmock.h"
|
|
||||||
|
|
||||||
using fmt::detail::max_value;
|
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
|
||||||
template <typename Char> struct numpunct : std::numpunct<Char> {
|
|
||||||
protected:
|
|
||||||
Char do_decimal_point() const FMT_OVERRIDE { return '?'; }
|
|
||||||
std::string do_grouping() const FMT_OVERRIDE { return "\03"; }
|
|
||||||
Char do_thousands_sep() const FMT_OVERRIDE { return '~'; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char> struct no_grouping : std::numpunct<Char> {
|
|
||||||
protected:
|
|
||||||
Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
|
|
||||||
std::string do_grouping() const FMT_OVERRIDE { return ""; }
|
|
||||||
Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char> struct special_grouping : std::numpunct<Char> {
|
|
||||||
protected:
|
|
||||||
Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
|
|
||||||
std::string do_grouping() const FMT_OVERRIDE { return "\03\02"; }
|
|
||||||
Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char> struct small_grouping : std::numpunct<Char> {
|
|
||||||
protected:
|
|
||||||
Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
|
|
||||||
std::string do_grouping() const FMT_OVERRIDE { return "\01"; }
|
|
||||||
Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(LocaleTest, DoubleDecimalPoint) {
|
|
||||||
std::locale loc(std::locale(), new numpunct<char>());
|
|
||||||
EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LocaleTest, Format) {
|
|
||||||
std::locale loc(std::locale(), new numpunct<char>());
|
|
||||||
EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567));
|
|
||||||
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
|
|
||||||
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
|
|
||||||
fmt::format_arg_store<fmt::format_context, int> as{1234567};
|
|
||||||
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
|
|
||||||
std::string s;
|
|
||||||
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
|
|
||||||
EXPECT_EQ("1~234~567", s);
|
|
||||||
|
|
||||||
std::locale no_grouping_loc(std::locale(), new no_grouping<char>());
|
|
||||||
EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:L}", 1234567));
|
|
||||||
|
|
||||||
std::locale special_grouping_loc(std::locale(), new special_grouping<char>());
|
|
||||||
EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678));
|
|
||||||
EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345));
|
|
||||||
|
|
||||||
std::locale small_grouping_loc(std::locale(), new small_grouping<char>());
|
|
||||||
EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
|
|
||||||
fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LocaleTest, FormatDetaultAlign) {
|
|
||||||
std::locale special_grouping_loc(std::locale(), new special_grouping<char>());
|
|
||||||
EXPECT_EQ(" 12,345", fmt::format(special_grouping_loc, "{:8L}", 12345));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LocaleTest, WFormat) {
|
|
||||||
std::locale loc(std::locale(), new numpunct<wchar_t>());
|
|
||||||
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
|
|
||||||
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
|
|
||||||
fmt::format_arg_store<fmt::wformat_context, int> as{1234567};
|
|
||||||
EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:L}", fmt::wformat_args(as)));
|
|
||||||
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
|
|
||||||
|
|
||||||
std::locale no_grouping_loc(std::locale(), new no_grouping<wchar_t>());
|
|
||||||
EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:L}", 1234567));
|
|
||||||
|
|
||||||
std::locale special_grouping_loc(std::locale(),
|
|
||||||
new special_grouping<wchar_t>());
|
|
||||||
EXPECT_EQ(L"1,23,45,678",
|
|
||||||
fmt::format(special_grouping_loc, L"{:L}", 12345678));
|
|
||||||
|
|
||||||
std::locale small_grouping_loc(std::locale(), new small_grouping<wchar_t>());
|
|
||||||
EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5",
|
|
||||||
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LocaleTest, DoubleFormatter) {
|
|
||||||
auto loc = std::locale(std::locale(), new special_grouping<char>());
|
|
||||||
auto f = fmt::formatter<int>();
|
|
||||||
auto parse_ctx = fmt::format_parse_context("L");
|
|
||||||
f.parse(parse_ctx);
|
|
||||||
char buf[10] = {};
|
|
||||||
fmt::basic_format_context<char*, char> format_ctx(
|
|
||||||
buf, {}, fmt::detail::locale_ref(loc));
|
|
||||||
*f.format(12345, format_ctx) = 0;
|
|
||||||
EXPECT_STREQ("12,345", buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
|
|
@ -8,14 +8,18 @@
|
||||||
#ifndef FMT_MOCK_ALLOCATOR_H_
|
#ifndef FMT_MOCK_ALLOCATOR_H_
|
||||||
#define FMT_MOCK_ALLOCATOR_H_
|
#define FMT_MOCK_ALLOCATOR_H_
|
||||||
|
|
||||||
#include "fmt/format.h"
|
#include <assert.h> // assert
|
||||||
#include "gmock.h"
|
#include <stddef.h> // size_t
|
||||||
|
|
||||||
|
#include <memory> // std::allocator_traits
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
template <typename T> class mock_allocator {
|
template <typename T> class mock_allocator {
|
||||||
public:
|
public:
|
||||||
mock_allocator() {}
|
mock_allocator() {}
|
||||||
mock_allocator(const mock_allocator&) {}
|
mock_allocator(const mock_allocator&) {}
|
||||||
typedef T value_type;
|
using value_type = T;
|
||||||
MOCK_METHOD1_T(allocate, T*(size_t n));
|
MOCK_METHOD1_T(allocate, T*(size_t n));
|
||||||
MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
|
MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
|
||||||
};
|
};
|
||||||
|
@ -30,7 +34,7 @@ template <typename Allocator> class allocator_ref {
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef typename Allocator::value_type value_type;
|
using value_type = typename Allocator::value_type;
|
||||||
|
|
||||||
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
|
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
|
||||||
|
|
||||||
|
|
565
test/module-test.cc
Normal file
565
test/module-test.cc
Normal file
|
@ -0,0 +1,565 @@
|
||||||
|
// Formatting library for C++ - module tests
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2021 - present, Daniela Engert
|
||||||
|
// All Rights Reserved
|
||||||
|
// {fmt} module.
|
||||||
|
|
||||||
|
#ifdef _MSC_FULL_VER
|
||||||
|
// hide some implementation bugs in msvc
|
||||||
|
// that are not essential to users of the module.
|
||||||
|
# define FMT_HIDE_MODULE_BUGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <chrono>
|
||||||
|
#include <exception>
|
||||||
|
#include <iterator>
|
||||||
|
#include <locale>
|
||||||
|
#include <memory>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
|
||||||
|
defined(__linux__)) && \
|
||||||
|
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||||
|
# include <fcntl.h>
|
||||||
|
# define FMT_USE_FCNTL 1
|
||||||
|
#else
|
||||||
|
# define FMT_USE_FCNTL 0
|
||||||
|
#endif
|
||||||
|
#define FMT_NOEXCEPT noexcept
|
||||||
|
|
||||||
|
import fmt;
|
||||||
|
|
||||||
|
// check for macros leaking from BMI
|
||||||
|
static bool macro_leaked =
|
||||||
|
#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H)
|
||||||
|
true;
|
||||||
|
#else
|
||||||
|
false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "gtest-extra.h"
|
||||||
|
|
||||||
|
// an implicitly exported namespace must be visible [module.interface]/2.2
|
||||||
|
TEST(module_test, namespace) {
|
||||||
|
using namespace fmt;
|
||||||
|
using namespace fmt::literals;
|
||||||
|
ASSERT_TRUE(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
bool oops_detail_namespace_is_visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace fmt {
|
||||||
|
bool namespace_detail_invisible() {
|
||||||
|
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
|
||||||
|
_MSC_FULL_VER <= 192930129
|
||||||
|
// bug in msvc up to 16.11-pre1:
|
||||||
|
// the namespace is visible even when it is neither
|
||||||
|
// implicitly nor explicitly exported
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
using namespace detail;
|
||||||
|
// this fails to compile if fmt::detail is visible
|
||||||
|
return !oops_detail_namespace_is_visible;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace fmt
|
||||||
|
|
||||||
|
// the non-exported namespace 'detail' must be invisible [module.interface]/2
|
||||||
|
TEST(module_test, detail_namespace) {
|
||||||
|
EXPECT_TRUE(fmt::namespace_detail_invisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
// macros must not be imported from a *named* module [cpp.import]/5.1
|
||||||
|
TEST(module_test, macros) {
|
||||||
|
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
|
||||||
|
_MSC_FULL_VER <= 192930129
|
||||||
|
// bug in msvc up to 16.11-pre1:
|
||||||
|
// include-guard macros leak from BMI
|
||||||
|
// and even worse: they cannot be #undef-ined
|
||||||
|
macro_leaked = false;
|
||||||
|
#endif
|
||||||
|
EXPECT_FALSE(macro_leaked);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following is less about functional testing (that's done elsewhere)
|
||||||
|
// but rather visibility of all client-facing overloads, reachability of
|
||||||
|
// non-exported entities, name lookup and overload resolution within
|
||||||
|
// template instantitions.
|
||||||
|
// Excercise all exported entities of the API at least once.
|
||||||
|
// Instantiate as many code paths as possible.
|
||||||
|
|
||||||
|
TEST(module_test, to_string) {
|
||||||
|
EXPECT_EQ("42", fmt::to_string(42));
|
||||||
|
EXPECT_EQ("42", fmt::to_string(42.0));
|
||||||
|
|
||||||
|
EXPECT_EQ(L"42", fmt::to_wstring(42));
|
||||||
|
EXPECT_EQ(L"42", fmt::to_wstring(42.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, format) {
|
||||||
|
EXPECT_EQ("42", fmt::format("{:}", 42));
|
||||||
|
EXPECT_EQ("-42", fmt::format("{0}", -42.0));
|
||||||
|
|
||||||
|
EXPECT_EQ(L"42", fmt::format(L"{:}", 42));
|
||||||
|
EXPECT_EQ(L"-42", fmt::format(L"{0}", -42.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, format_to) {
|
||||||
|
std::string s;
|
||||||
|
fmt::format_to(std::back_inserter(s), "{}", 42);
|
||||||
|
EXPECT_EQ("42", s);
|
||||||
|
|
||||||
|
char buffer[4] = {0};
|
||||||
|
fmt::format_to(buffer, "{}", 42);
|
||||||
|
EXPECT_EQ("42", std::string_view(buffer));
|
||||||
|
|
||||||
|
fmt::memory_buffer mb;
|
||||||
|
fmt::format_to(mb, "{}", 42);
|
||||||
|
EXPECT_EQ("42", std::string_view(buffer));
|
||||||
|
|
||||||
|
std::wstring w;
|
||||||
|
fmt::format_to(std::back_inserter(w), L"{}", 42);
|
||||||
|
EXPECT_EQ(L"42", w);
|
||||||
|
|
||||||
|
wchar_t wbuffer[4] = {0};
|
||||||
|
fmt::format_to(wbuffer, L"{}", 42);
|
||||||
|
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||||
|
|
||||||
|
fmt::wmemory_buffer wb;
|
||||||
|
fmt::format_to(wb, L"{}", 42);
|
||||||
|
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, formatted_size) {
|
||||||
|
EXPECT_EQ(2u, fmt::formatted_size("{}", 42));
|
||||||
|
EXPECT_EQ(2u, fmt::formatted_size(L"{}", 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, format_to_n) {
|
||||||
|
std::string s;
|
||||||
|
auto result = fmt::format_to_n(std::back_inserter(s), 1, "{}", 42);
|
||||||
|
EXPECT_EQ(2u, result.size);
|
||||||
|
char buffer[4] = {0};
|
||||||
|
fmt::format_to_n(buffer, 3, "{}", 12345);
|
||||||
|
|
||||||
|
std::wstring w;
|
||||||
|
auto wresult = fmt::format_to_n(std::back_inserter(w), 1, L"{}", 42);
|
||||||
|
EXPECT_EQ(2u, wresult.size);
|
||||||
|
wchar_t wbuffer[4] = {0};
|
||||||
|
fmt::format_to_n(wbuffer, 3, L"{}", 12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, format_args) {
|
||||||
|
auto no_args = fmt::format_args();
|
||||||
|
EXPECT_FALSE(no_args.get(1));
|
||||||
|
|
||||||
|
fmt::basic_format_args args = fmt::make_format_args(42);
|
||||||
|
EXPECT_TRUE(args.max_size() > 0);
|
||||||
|
auto arg0 = args.get(0);
|
||||||
|
EXPECT_TRUE(arg0);
|
||||||
|
decltype(arg0) arg_none;
|
||||||
|
EXPECT_FALSE(arg_none);
|
||||||
|
EXPECT_TRUE(arg0.type() != arg_none.type());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, wformat_args) {
|
||||||
|
auto no_args = fmt::wformat_args();
|
||||||
|
EXPECT_FALSE(no_args.get(1));
|
||||||
|
fmt::basic_format_args args = fmt::make_wformat_args(42);
|
||||||
|
EXPECT_TRUE(args.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, checked_format_args) {
|
||||||
|
fmt::basic_format_args args = fmt::make_args_checked<int>("{}", 42);
|
||||||
|
EXPECT_TRUE(args.get(0));
|
||||||
|
fmt::basic_format_args wargs = fmt::make_args_checked<int>(L"{}", 42);
|
||||||
|
EXPECT_TRUE(wargs.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, dynamic_format_args) {
|
||||||
|
fmt::dynamic_format_arg_store<fmt::format_context> dyn_store;
|
||||||
|
dyn_store.push_back(fmt::arg("a42", 42));
|
||||||
|
fmt::basic_format_args args = dyn_store;
|
||||||
|
EXPECT_FALSE(args.get(3));
|
||||||
|
EXPECT_TRUE(args.get(fmt::string_view("a42")));
|
||||||
|
|
||||||
|
fmt::dynamic_format_arg_store<fmt::wformat_context> wdyn_store;
|
||||||
|
wdyn_store.push_back(fmt::arg(L"a42", 42));
|
||||||
|
fmt::basic_format_args wargs = wdyn_store;
|
||||||
|
EXPECT_FALSE(wargs.get(3));
|
||||||
|
EXPECT_TRUE(wargs.get(fmt::wstring_view(L"a42")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, vformat) {
|
||||||
|
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
|
||||||
|
EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"),
|
||||||
|
fmt::make_wformat_args(42)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, vformat_to) {
|
||||||
|
auto store = fmt::make_format_args(42);
|
||||||
|
std::string s;
|
||||||
|
fmt::vformat_to(std::back_inserter(s), "{}", store);
|
||||||
|
EXPECT_EQ("42", s);
|
||||||
|
|
||||||
|
char buffer[4] = {0};
|
||||||
|
fmt::vformat_to(buffer, "{:}", store);
|
||||||
|
EXPECT_EQ("42", std::string_view(buffer));
|
||||||
|
|
||||||
|
auto wstore = fmt::make_wformat_args(42);
|
||||||
|
std::wstring w;
|
||||||
|
fmt::vformat_to(std::back_inserter(w), L"{}", wstore);
|
||||||
|
EXPECT_EQ(L"42", w);
|
||||||
|
|
||||||
|
wchar_t wbuffer[4] = {0};
|
||||||
|
fmt::vformat_to(wbuffer, L"{:}", wstore);
|
||||||
|
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, vformat_to_n) {
|
||||||
|
auto store = fmt::make_format_args(12345);
|
||||||
|
std::string s;
|
||||||
|
auto result = fmt::vformat_to_n(std::back_inserter(s), 1, "{}", store);
|
||||||
|
char buffer[4] = {0};
|
||||||
|
fmt::vformat_to_n(buffer, 3, "{:}", store);
|
||||||
|
|
||||||
|
auto wstore = fmt::make_wformat_args(12345);
|
||||||
|
std::wstring w;
|
||||||
|
auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1,
|
||||||
|
fmt::to_string_view(L"{}"), wstore);
|
||||||
|
wchar_t wbuffer[4] = {0};
|
||||||
|
fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string as_string(std::wstring_view text) {
|
||||||
|
return {reinterpret_cast<const char*>(text.data()),
|
||||||
|
text.size() * sizeof(text[0])};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, print) {
|
||||||
|
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
|
||||||
|
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(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, vprint) {
|
||||||
|
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
|
||||||
|
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
|
||||||
|
"4.2");
|
||||||
|
if (false) {
|
||||||
|
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
|
||||||
|
as_string(L"42µ"));
|
||||||
|
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
|
||||||
|
as_string(L"42"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, named_args) {
|
||||||
|
EXPECT_EQ("42", fmt::format("{answer}", fmt::arg("answer", 42)));
|
||||||
|
EXPECT_EQ(L"42", fmt::format(L"{answer}", fmt::arg(L"answer", 42)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, literals) {
|
||||||
|
using namespace fmt::literals;
|
||||||
|
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", L"{}"_format(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, locale) {
|
||||||
|
auto store = fmt::make_format_args(4.2);
|
||||||
|
const auto classic = std::locale::classic();
|
||||||
|
EXPECT_EQ("4.2", fmt::format(classic, "{:L}", 4.2));
|
||||||
|
EXPECT_EQ("4.2", fmt::vformat(classic, "{:L}", store));
|
||||||
|
std::string s;
|
||||||
|
fmt::vformat_to(std::back_inserter(s), classic, "{:L}", store);
|
||||||
|
EXPECT_EQ("4.2", s);
|
||||||
|
EXPECT_EQ("4.2", fmt::format("{:L}", 4.2));
|
||||||
|
|
||||||
|
auto wstore = fmt::make_wformat_args(4.2);
|
||||||
|
EXPECT_EQ(L"4.2", fmt::format(classic, L"{:L}", 4.2));
|
||||||
|
EXPECT_EQ(L"4.2", fmt::vformat(classic, L"{:L}", wstore));
|
||||||
|
std::wstring w;
|
||||||
|
fmt::vformat_to(std::back_inserter(w), classic, L"{:L}", wstore);
|
||||||
|
EXPECT_EQ(L"4.2", w);
|
||||||
|
EXPECT_EQ(L"4.2", fmt::format(L"{:L}", 4.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, string_view) {
|
||||||
|
fmt::string_view nsv("fmt");
|
||||||
|
EXPECT_EQ("fmt", nsv);
|
||||||
|
EXPECT_TRUE(fmt::string_view("fmt") == nsv);
|
||||||
|
|
||||||
|
fmt::wstring_view wsv(L"fmt");
|
||||||
|
EXPECT_EQ(L"fmt", wsv);
|
||||||
|
EXPECT_TRUE(fmt::wstring_view(L"fmt") == wsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, memory_buffer) {
|
||||||
|
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
|
||||||
|
fmt::format_to(buffer, "{}", "42");
|
||||||
|
EXPECT_EQ("42", to_string(buffer));
|
||||||
|
fmt::memory_buffer nbuffer(std::move(buffer));
|
||||||
|
EXPECT_EQ("42", to_string(nbuffer));
|
||||||
|
buffer = std::move(nbuffer);
|
||||||
|
EXPECT_EQ("42", to_string(buffer));
|
||||||
|
nbuffer.clear();
|
||||||
|
EXPECT_EQ(0u, to_string(nbuffer).size());
|
||||||
|
|
||||||
|
fmt::wmemory_buffer wbuffer;
|
||||||
|
EXPECT_EQ(0u, to_string(wbuffer).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, is_char) {
|
||||||
|
EXPECT_TRUE(fmt::is_char<char>());
|
||||||
|
EXPECT_TRUE(fmt::is_char<wchar_t>());
|
||||||
|
EXPECT_TRUE(fmt::is_char<char8_t>());
|
||||||
|
EXPECT_TRUE(fmt::is_char<char16_t>());
|
||||||
|
EXPECT_TRUE(fmt::is_char<char32_t>());
|
||||||
|
EXPECT_FALSE(fmt::is_char<signed char>());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, ptr) {
|
||||||
|
uintptr_t answer = 42;
|
||||||
|
auto p = std::bit_cast<int*>(answer);
|
||||||
|
EXPECT_EQ("0x2a", fmt::to_string(fmt::ptr(p)));
|
||||||
|
std::unique_ptr<int> up(p);
|
||||||
|
EXPECT_EQ("0x2a", fmt::to_string(fmt::ptr(up)));
|
||||||
|
up.release();
|
||||||
|
auto sp = std::make_shared<int>(0);
|
||||||
|
p = sp.get();
|
||||||
|
EXPECT_EQ(fmt::to_string(fmt::ptr(p)), fmt::to_string(fmt::ptr(sp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, errors) {
|
||||||
|
auto store = fmt::make_format_args(42);
|
||||||
|
EXPECT_THROW(throw fmt::format_error("oops"), std::exception);
|
||||||
|
EXPECT_THROW(throw fmt::vsystem_error(0, "{}", store), std::system_error);
|
||||||
|
EXPECT_THROW(throw fmt::system_error(0, "{}", 42), std::system_error);
|
||||||
|
|
||||||
|
fmt::memory_buffer buffer;
|
||||||
|
fmt::format_system_error(buffer, 0, "oops");
|
||||||
|
auto oops = to_string(buffer);
|
||||||
|
EXPECT_TRUE(oops.size() > 0);
|
||||||
|
EXPECT_WRITE(stderr, fmt::report_system_error(0, "oops"), oops + '\n');
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
EXPECT_THROW(throw fmt::vwindows_error(0, "{}", store), std::system_error);
|
||||||
|
EXPECT_THROW(throw fmt::windows_error(0, "{}", 42), std::system_error);
|
||||||
|
output_redirect redirect(stderr);
|
||||||
|
fmt::report_windows_error(0, "oops");
|
||||||
|
EXPECT_TRUE(redirect.restore_and_read().size() > 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, error_code) {
|
||||||
|
EXPECT_EQ("generic:42",
|
||||||
|
fmt::format("{0}", std::error_code(42, std::generic_category())));
|
||||||
|
EXPECT_EQ("system:42",
|
||||||
|
fmt::format("{0}", std::error_code(42, fmt::system_category())));
|
||||||
|
EXPECT_EQ(L"generic:42",
|
||||||
|
fmt::format(L"{0}", std::error_code(42, std::generic_category())));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, format_int) {
|
||||||
|
fmt::format_int sanswer(42);
|
||||||
|
EXPECT_EQ("42", fmt::string_view(sanswer.data(), sanswer.size()));
|
||||||
|
fmt::format_int uanswer(42u);
|
||||||
|
EXPECT_EQ("42", fmt::string_view(uanswer.data(), uanswer.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct test_formatter : fmt::formatter<char> {
|
||||||
|
bool check() { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct test_dynamic_formatter : fmt::dynamic_formatter<> {
|
||||||
|
bool check() { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(module_test, formatter) {
|
||||||
|
EXPECT_TRUE(test_formatter{}.check());
|
||||||
|
EXPECT_TRUE(test_dynamic_formatter{}.check());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, join) {
|
||||||
|
int arr[3] = {1, 2, 3};
|
||||||
|
std::vector<double> vec{1.0, 2.0, 3.0};
|
||||||
|
std::initializer_list<int> il{1, 2, 3};
|
||||||
|
auto sep = fmt::to_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, 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(il, sep)));
|
||||||
|
|
||||||
|
auto wsep = fmt::to_string_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, wsep)));
|
||||||
|
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, time) {
|
||||||
|
auto time_now = std::time(nullptr);
|
||||||
|
EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120);
|
||||||
|
EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, time_point) {
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
std::string_view past("2021-05-20 10:30:15");
|
||||||
|
EXPECT_TRUE(past < fmt::format("{:%Y-%m-%d %H:%M:%S}", now));
|
||||||
|
std::wstring_view wpast(L"2021-05-20 10:30:15");
|
||||||
|
EXPECT_TRUE(wpast < fmt::format(L"{:%Y-%m-%d %H:%M:%S}", now));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, time_duration) {
|
||||||
|
using us = std::chrono::duration<double, std::micro>;
|
||||||
|
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds{42}));
|
||||||
|
EXPECT_EQ("4.2µs", fmt::format("{:3.1}", us{4.234}));
|
||||||
|
EXPECT_EQ("4.2µs", fmt::format(std::locale::classic(), "{:L}", us{4.2}));
|
||||||
|
|
||||||
|
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds{42}));
|
||||||
|
EXPECT_EQ(L"4.2µs", fmt::format(L"{:3.1}", us{4.234}));
|
||||||
|
EXPECT_EQ(L"4.2µs", fmt::format(std::locale::classic(), L"{:L}", us{4.2}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, weekday) {
|
||||||
|
EXPECT_EQ("Monday",
|
||||||
|
std::format(std::locale::classic(), "{:%A}", 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) {
|
||||||
|
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
|
||||||
|
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"%d", 42), as_string(L"42"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, fprintf) {
|
||||||
|
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"));
|
||||||
|
std::wostringstream ws;
|
||||||
|
fmt::fprintf(ws, L"%s", L"bla");
|
||||||
|
EXPECT_EQ(L"bla", ws.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, sprintf) {
|
||||||
|
EXPECT_EQ("42", fmt::sprintf("%d", 42));
|
||||||
|
EXPECT_EQ(L"42", fmt::sprintf(L"%d", 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, vprintf) {
|
||||||
|
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)),
|
||||||
|
as_string(L"42"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, vfprintf) {
|
||||||
|
auto args = fmt::make_printf_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);
|
||||||
|
if (false) {
|
||||||
|
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) {
|
||||||
|
EXPECT_EQ("42", fmt::vsprintf("%d", fmt::make_printf_args(42)));
|
||||||
|
EXPECT_EQ(L"42", fmt::vsprintf(L"%d", fmt::make_wprintf_args(42)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, color) {
|
||||||
|
auto fg_check = fg(fmt::rgb(255, 200, 30));
|
||||||
|
auto bg_check = bg(fmt::color::dark_slate_gray) | fmt::emphasis::italic;
|
||||||
|
auto emphasis_check = fmt::emphasis::underline | fmt::emphasis::bold;
|
||||||
|
EXPECT_EQ("\x1B[30m42\x1B[0m",
|
||||||
|
fmt::format(fg(fmt::terminal_color::black), "{}", 42));
|
||||||
|
EXPECT_EQ(L"\x1B[30m42\x1B[0m",
|
||||||
|
fmt::format(fg(fmt::terminal_color::black), L"{}", 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, cstring_view) {
|
||||||
|
auto s = "fmt";
|
||||||
|
EXPECT_EQ(s, fmt::cstring_view(s).c_str());
|
||||||
|
auto w = L"fmt";
|
||||||
|
EXPECT_EQ(w, fmt::wcstring_view(w).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, buffered_file) {
|
||||||
|
EXPECT_TRUE(fmt::buffered_file{}.get() == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(module_test, output_file) {
|
||||||
|
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct custom_context {
|
||||||
|
using char_type = char;
|
||||||
|
using parse_context_type = fmt::format_parse_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(module_test, custom_context) {
|
||||||
|
fmt::basic_format_arg<custom_context> 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) {
|
||||||
|
using namespace fmt::literals;
|
||||||
|
EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42));
|
||||||
|
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(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42"));
|
||||||
|
}
|
300
test/os-test.cc
300
test/os-test.cc
|
@ -19,20 +19,21 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using fmt::buffered_file;
|
using fmt::buffered_file;
|
||||||
using fmt::error_code;
|
using testing::HasSubstr;
|
||||||
|
using wstring_view = fmt::basic_string_view<wchar_t>;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
|
|
||||||
TEST(UtilTest, UTF16ToUTF8) {
|
TEST(util_test, utf16_to_utf8) {
|
||||||
std::string s = "ёжик";
|
auto s = std::string("ёжик");
|
||||||
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
|
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
|
||||||
EXPECT_EQ(s, u.str());
|
EXPECT_EQ(s, u.str());
|
||||||
EXPECT_EQ(s.size(), u.size());
|
EXPECT_EQ(s.size(), u.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, UTF16ToUTF8EmptyString) {
|
TEST(util_test, utf16_to_utf8_empty_string) {
|
||||||
std::string s = "";
|
std::string s = "";
|
||||||
fmt::detail::utf16_to_utf8 u(L"");
|
fmt::detail::utf16_to_utf8 u(L"");
|
||||||
EXPECT_EQ(s, u.str());
|
EXPECT_EQ(s, u.str());
|
||||||
|
@ -45,65 +46,73 @@ void check_utf_conversion_error(
|
||||||
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
|
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
|
||||||
fmt::memory_buffer out;
|
fmt::memory_buffer out;
|
||||||
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
|
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
|
||||||
fmt::system_error error(0, "");
|
auto error = std::system_error(std::error_code());
|
||||||
try {
|
try {
|
||||||
(Converter)(str);
|
(Converter)(str);
|
||||||
} catch (const fmt::system_error& e) {
|
} catch (const std::system_error& e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
|
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value());
|
||||||
EXPECT_EQ(fmt::to_string(out), error.what());
|
EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, UTF16ToUTF8Error) {
|
TEST(util_test, utf16_to_utf8_error) {
|
||||||
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
|
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
|
||||||
"cannot convert string from UTF-16 to UTF-8");
|
"cannot convert string from UTF-16 to UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, UTF16ToUTF8Convert) {
|
TEST(util_test, utf16_to_utf8_convert) {
|
||||||
fmt::detail::utf16_to_utf8 u;
|
fmt::detail::utf16_to_utf8 u;
|
||||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1)));
|
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(0, 1)));
|
||||||
EXPECT_EQ(ERROR_INVALID_PARAMETER,
|
EXPECT_EQ(ERROR_INVALID_PARAMETER,
|
||||||
u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u)));
|
u.convert(wstring_view(L"foo", INT_MAX + 1u)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, FormatWindowsError) {
|
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) {
|
||||||
LPWSTR message = 0;
|
LPWSTR message = 0;
|
||||||
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
auto result = FormatMessageW(
|
||||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
0, ERROR_FILE_EXISTS,
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
0, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
reinterpret_cast<LPWSTR>(&message), 0, 0);
|
reinterpret_cast<LPWSTR>(&message), 0, 0);
|
||||||
fmt::detail::utf16_to_utf8 utf8_message(message);
|
fmt::detail::utf16_to_utf8 utf8_message(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");
|
||||||
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
|
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
|
||||||
fmt::to_string(actual_message));
|
fmt::to_string(actual_message));
|
||||||
actual_message.resize(0);
|
actual_message.resize(0);
|
||||||
auto max_size = fmt::detail::max_value<size_t>();
|
|
||||||
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS,
|
|
||||||
fmt::string_view(0, max_size));
|
|
||||||
EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
|
|
||||||
fmt::to_string(actual_message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, FormatLongWindowsError) {
|
TEST(os_test, format_long_windows_error) {
|
||||||
LPWSTR message = 0;
|
LPWSTR message = 0;
|
||||||
// this error code is not available on all Windows platforms and
|
// this error code is not available on all Windows platforms and
|
||||||
// Windows SDKs, so do not fail the test if the error string cannot
|
// Windows SDKs, so do not fail the test if the error string cannot
|
||||||
// be retrieved.
|
// be retrieved.
|
||||||
const int provisioning_not_allowed =
|
int provisioning_not_allowed = 0x80284013L; // TBS_E_PROVISIONING_NOT_ALLOWED
|
||||||
0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/;
|
auto result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||||
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
0, static_cast<DWORD>(provisioning_not_allowed),
|
||||||
0, static_cast<DWORD>(provisioning_not_allowed),
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
reinterpret_cast<LPWSTR>(&message), 0, 0);
|
||||||
reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) {
|
if (result == 0) {
|
||||||
|
LocalFree(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fmt::detail::utf16_to_utf8 utf8_message(message);
|
fmt::detail::utf16_to_utf8 utf8_message(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,
|
||||||
|
@ -112,20 +121,20 @@ TEST(UtilTest, FormatLongWindowsError) {
|
||||||
fmt::to_string(actual_message));
|
fmt::to_string(actual_message));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, WindowsError) {
|
TEST(os_test, windows_error) {
|
||||||
fmt::system_error error(0, "");
|
auto error = std::system_error(std::error_code());
|
||||||
try {
|
try {
|
||||||
throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error");
|
throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error");
|
||||||
} catch (const fmt::system_error& e) {
|
} catch (const std::system_error& e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
fmt::memory_buffer message;
|
fmt::memory_buffer message;
|
||||||
fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
|
fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
|
||||||
EXPECT_EQ(to_string(message), error.what());
|
EXPECT_THAT(error.what(), HasSubstr(to_string(message)));
|
||||||
EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code());
|
EXPECT_EQ(ERROR_FILE_EXISTS, error.code().value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UtilTest, ReportWindowsError) {
|
TEST(os_test, report_windows_error) {
|
||||||
fmt::memory_buffer out;
|
fmt::memory_buffer out;
|
||||||
fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
|
fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
|
||||||
out.push_back('\n');
|
out.push_back('\n');
|
||||||
|
@ -140,30 +149,24 @@ TEST(UtilTest, ReportWindowsError) {
|
||||||
|
|
||||||
using fmt::file;
|
using fmt::file;
|
||||||
|
|
||||||
// Checks if the file is open by reading one character from it.
|
bool isclosed(int fd) {
|
||||||
static bool isopen(int fd) {
|
|
||||||
char buffer;
|
char buffer;
|
||||||
return FMT_POSIX(read(fd, &buffer, 1)) == 1;
|
auto result = std::streamsize();
|
||||||
}
|
|
||||||
|
|
||||||
static bool isclosed(int fd) {
|
|
||||||
char buffer;
|
|
||||||
std::streamsize result = 0;
|
|
||||||
SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1)));
|
SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1)));
|
||||||
return result == -1 && errno == EBADF;
|
return result == -1 && errno == EBADF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opens a file for reading.
|
// Opens a file for reading.
|
||||||
static file open_file() {
|
file open_file() {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT));
|
write_end.write(file_content, std::strlen(file_content));
|
||||||
write_end.close();
|
write_end.close();
|
||||||
return read_end;
|
return read_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempts to write a string to a file.
|
// Attempts to write a string to a file.
|
||||||
static void write(file& f, fmt::string_view s) {
|
void write(file& f, fmt::string_view s) {
|
||||||
size_t num_chars_left = s.size();
|
size_t num_chars_left = s.size();
|
||||||
const char* ptr = s.data();
|
const char* ptr = s.data();
|
||||||
do {
|
do {
|
||||||
|
@ -175,12 +178,12 @@ static void write(file& f, fmt::string_view s) {
|
||||||
} while (num_chars_left != 0);
|
} while (num_chars_left != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, DefaultCtor) {
|
TEST(buffered_file_test, default_ctor) {
|
||||||
buffered_file f;
|
auto f = buffered_file();
|
||||||
EXPECT_TRUE(f.get() == nullptr);
|
EXPECT_TRUE(f.get() == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, MoveCtor) {
|
TEST(buffered_file_test, move_ctor) {
|
||||||
buffered_file bf = open_buffered_file();
|
buffered_file bf = open_buffered_file();
|
||||||
FILE* fp = bf.get();
|
FILE* fp = bf.get();
|
||||||
EXPECT_TRUE(fp != nullptr);
|
EXPECT_TRUE(fp != nullptr);
|
||||||
|
@ -189,7 +192,7 @@ TEST(BufferedFileTest, MoveCtor) {
|
||||||
EXPECT_TRUE(bf.get() == nullptr);
|
EXPECT_TRUE(bf.get() == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, MoveAssignment) {
|
TEST(buffered_file_test, move_assignment) {
|
||||||
buffered_file bf = open_buffered_file();
|
buffered_file bf = open_buffered_file();
|
||||||
FILE* fp = bf.get();
|
FILE* fp = bf.get();
|
||||||
EXPECT_TRUE(fp != nullptr);
|
EXPECT_TRUE(fp != nullptr);
|
||||||
|
@ -199,7 +202,7 @@ TEST(BufferedFileTest, MoveAssignment) {
|
||||||
EXPECT_TRUE(bf.get() == nullptr);
|
EXPECT_TRUE(bf.get() == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, MoveAssignmentClosesFile) {
|
TEST(buffered_file_test, move_assignment_closes_file) {
|
||||||
buffered_file bf = open_buffered_file();
|
buffered_file bf = open_buffered_file();
|
||||||
buffered_file bf2 = open_buffered_file();
|
buffered_file bf2 = open_buffered_file();
|
||||||
int old_fd = bf2.fileno();
|
int old_fd = bf2.fileno();
|
||||||
|
@ -207,27 +210,27 @@ TEST(BufferedFileTest, MoveAssignmentClosesFile) {
|
||||||
EXPECT_TRUE(isclosed(old_fd));
|
EXPECT_TRUE(isclosed(old_fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, MoveFromTemporaryInCtor) {
|
TEST(buffered_file_test, move_from_temporary_in_ctor) {
|
||||||
FILE* fp = nullptr;
|
FILE* fp = nullptr;
|
||||||
buffered_file f(open_buffered_file(&fp));
|
buffered_file f = open_buffered_file(&fp);
|
||||||
EXPECT_EQ(fp, f.get());
|
EXPECT_EQ(fp, f.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, MoveFromTemporaryInAssignment) {
|
TEST(buffered_file_test, move_from_temporary_in_assignment) {
|
||||||
FILE* fp = nullptr;
|
FILE* fp = nullptr;
|
||||||
buffered_file f;
|
auto f = buffered_file();
|
||||||
f = open_buffered_file(&fp);
|
f = open_buffered_file(&fp);
|
||||||
EXPECT_EQ(fp, f.get());
|
EXPECT_EQ(fp, f.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) {
|
TEST(buffered_file_test, move_from_temporary_in_assignment_closes_file) {
|
||||||
buffered_file f = open_buffered_file();
|
buffered_file f = open_buffered_file();
|
||||||
int old_fd = f.fileno();
|
int old_fd = f.fileno();
|
||||||
f = open_buffered_file();
|
f = open_buffered_file();
|
||||||
EXPECT_TRUE(isclosed(old_fd));
|
EXPECT_TRUE(isclosed(old_fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, CloseFileInDtor) {
|
TEST(buffered_file_test, close_file_in_dtor) {
|
||||||
int fd = 0;
|
int fd = 0;
|
||||||
{
|
{
|
||||||
buffered_file f = open_buffered_file();
|
buffered_file f = open_buffered_file();
|
||||||
|
@ -236,8 +239,9 @@ TEST(BufferedFileTest, CloseFileInDtor) {
|
||||||
EXPECT_TRUE(isclosed(fd));
|
EXPECT_TRUE(isclosed(fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, CloseErrorInDtor) {
|
TEST(buffered_file_test, close_error_in_dtor) {
|
||||||
std::unique_ptr<buffered_file> f(new buffered_file(open_buffered_file()));
|
auto f =
|
||||||
|
std::unique_ptr<buffered_file>(new buffered_file(open_buffered_file()));
|
||||||
EXPECT_WRITE(
|
EXPECT_WRITE(
|
||||||
stderr,
|
stderr,
|
||||||
{
|
{
|
||||||
|
@ -248,10 +252,10 @@ TEST(BufferedFileTest, CloseErrorInDtor) {
|
||||||
FMT_POSIX(close(f->fileno()));
|
FMT_POSIX(close(f->fileno()));
|
||||||
SUPPRESS_ASSERT(f.reset(nullptr));
|
SUPPRESS_ASSERT(f.reset(nullptr));
|
||||||
},
|
},
|
||||||
format_system_error(EBADF, "cannot close file") + "\n");
|
system_error_message(EBADF, "cannot close file") + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, Close) {
|
TEST(buffered_file_test, close) {
|
||||||
buffered_file f = open_buffered_file();
|
buffered_file f = open_buffered_file();
|
||||||
int fd = f.fileno();
|
int fd = f.fileno();
|
||||||
f.close();
|
f.close();
|
||||||
|
@ -259,73 +263,101 @@ TEST(BufferedFileTest, Close) {
|
||||||
EXPECT_TRUE(isclosed(fd));
|
EXPECT_TRUE(isclosed(fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, CloseError) {
|
TEST(buffered_file_test, close_error) {
|
||||||
buffered_file f = open_buffered_file();
|
buffered_file f = open_buffered_file();
|
||||||
FMT_POSIX(close(f.fileno()));
|
FMT_POSIX(close(f.fileno()));
|
||||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
|
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
|
||||||
EXPECT_TRUE(f.get() == nullptr);
|
EXPECT_TRUE(f.get() == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, Fileno) {
|
TEST(buffered_file_test, fileno) {
|
||||||
buffered_file f;
|
auto f = open_buffered_file();
|
||||||
# ifndef __COVERITY__
|
|
||||||
// fileno on a null FILE pointer either crashes or returns an error.
|
|
||||||
// Disable Coverity because this is intentional.
|
|
||||||
EXPECT_DEATH_IF_SUPPORTED(
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
f.fileno();
|
|
||||||
} catch (const fmt::system_error&) {
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"");
|
|
||||||
# endif
|
|
||||||
f = open_buffered_file();
|
|
||||||
EXPECT_TRUE(f.fileno() != -1);
|
EXPECT_TRUE(f.fileno() != -1);
|
||||||
file copy = file::dup(f.fileno());
|
file copy = file::dup(f.fileno());
|
||||||
EXPECT_READ(copy, FILE_CONTENT);
|
EXPECT_READ(copy, file_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DirectBufferedFileTest, Print) {
|
TEST(ostream_test, move) {
|
||||||
fmt::direct_buffered_file out(
|
fmt::ostream out = fmt::output_file("test-file");
|
||||||
"test-file", fmt::file::WRONLY | fmt::file::CREATE);
|
fmt::ostream moved(std::move(out));
|
||||||
fmt::print(out, "The answer is {}.\n", 42);
|
moved.print("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ostream_test, move_while_holding_data) {
|
||||||
|
{
|
||||||
|
fmt::ostream out = fmt::output_file("test-file");
|
||||||
|
out.print("Hello, ");
|
||||||
|
fmt::ostream moved(std::move(out));
|
||||||
|
moved.print("world!\n");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
file in("test-file", file::RDONLY);
|
||||||
|
EXPECT_READ(in, "Hello, world!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ostream_test, print) {
|
||||||
|
fmt::ostream out = fmt::output_file("test-file");
|
||||||
|
out.print("The answer is {}.\n",
|
||||||
|
fmt::join(std::initializer_list<int>{42}, ", "));
|
||||||
out.close();
|
out.close();
|
||||||
file in("test-file", file::RDONLY);
|
file in("test-file", file::RDONLY);
|
||||||
EXPECT_READ(in, "The answer is 42.\n");
|
EXPECT_READ(in, "The answer is 42.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DirectBufferedFileTest, BufferBoundary) {
|
TEST(ostream_test, buffer_boundary) {
|
||||||
auto str = std::string(4096, 'x');
|
auto str = std::string(4096, 'x');
|
||||||
fmt::direct_buffered_file out(
|
fmt::ostream out = fmt::output_file("test-file");
|
||||||
"test-file", fmt::file::WRONLY | fmt::file::CREATE);
|
out.print("{}", str);
|
||||||
fmt::print(out, "{}", str);
|
out.print("{}", str);
|
||||||
fmt::print(out, "{}", str);
|
|
||||||
out.close();
|
out.close();
|
||||||
file in("test-file", file::RDONLY);
|
file in("test-file", file::RDONLY);
|
||||||
EXPECT_READ(in, str + str);
|
EXPECT_READ(in, str + str);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, DefaultCtor) {
|
TEST(ostream_test, buffer_size) {
|
||||||
|
fmt::ostream out = fmt::output_file("test-file", fmt::buffer_size = 1);
|
||||||
|
out.print("{}", "foo");
|
||||||
|
out.close();
|
||||||
|
file in("test-file", file::RDONLY);
|
||||||
|
EXPECT_READ(in, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ostream_test, truncate) {
|
||||||
|
{
|
||||||
|
fmt::ostream out = fmt::output_file("test-file");
|
||||||
|
out.print("0123456789");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fmt::ostream out = fmt::output_file("test-file");
|
||||||
|
out.print("foo");
|
||||||
|
}
|
||||||
|
file in("test-file", file::RDONLY);
|
||||||
|
EXPECT_EQ("foo", read(in, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(file_test, default_ctor) {
|
||||||
file f;
|
file f;
|
||||||
EXPECT_EQ(-1, f.descriptor());
|
EXPECT_EQ(-1, f.descriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, OpenBufferedFileInCtor) {
|
TEST(file_test, open_buffered_file_in_ctor) {
|
||||||
FILE* fp = safe_fopen("test-file", "w");
|
FILE* fp = safe_fopen("test-file", "w");
|
||||||
std::fputs(FILE_CONTENT, fp);
|
std::fputs(file_content, fp);
|
||||||
std::fclose(fp);
|
std::fclose(fp);
|
||||||
file f("test-file", file::RDONLY);
|
file f("test-file", file::RDONLY);
|
||||||
ASSERT_TRUE(isopen(f.descriptor()));
|
// Check if the file is open by reading one character from it.
|
||||||
|
char buffer;
|
||||||
|
bool isopen = FMT_POSIX(read(f.descriptor(), &buffer, 1)) == 1;
|
||||||
|
ASSERT_TRUE(isopen);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, OpenBufferedFileError) {
|
TEST(file_test, open_buffered_file_error) {
|
||||||
EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT,
|
EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT,
|
||||||
"cannot open file nonexistent");
|
"cannot open file nonexistent");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MoveCtor) {
|
TEST(file_test, move_ctor) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
int fd = f.descriptor();
|
int fd = f.descriptor();
|
||||||
EXPECT_NE(-1, fd);
|
EXPECT_NE(-1, fd);
|
||||||
|
@ -334,7 +366,7 @@ TEST(FileTest, MoveCtor) {
|
||||||
EXPECT_EQ(-1, f.descriptor());
|
EXPECT_EQ(-1, f.descriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MoveAssignment) {
|
TEST(file_test, move_assignment) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
int fd = f.descriptor();
|
int fd = f.descriptor();
|
||||||
EXPECT_NE(-1, fd);
|
EXPECT_NE(-1, fd);
|
||||||
|
@ -344,7 +376,7 @@ TEST(FileTest, MoveAssignment) {
|
||||||
EXPECT_EQ(-1, f.descriptor());
|
EXPECT_EQ(-1, f.descriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MoveAssignmentClosesFile) {
|
TEST(file_test, move_assignment_closes_file) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
file f2 = open_file();
|
file f2 = open_file();
|
||||||
int old_fd = f2.descriptor();
|
int old_fd = f2.descriptor();
|
||||||
|
@ -352,34 +384,34 @@ TEST(FileTest, MoveAssignmentClosesFile) {
|
||||||
EXPECT_TRUE(isclosed(old_fd));
|
EXPECT_TRUE(isclosed(old_fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static file OpenBufferedFile(int& fd) {
|
file open_buffered_file(int& fd) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
fd = f.descriptor();
|
fd = f.descriptor();
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MoveFromTemporaryInCtor) {
|
TEST(file_test, move_from_temporary_in_ctor) {
|
||||||
int fd = 0xdead;
|
int fd = 0xdead;
|
||||||
file f(OpenBufferedFile(fd));
|
file f(open_buffered_file(fd));
|
||||||
EXPECT_EQ(fd, f.descriptor());
|
EXPECT_EQ(fd, f.descriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MoveFromTemporaryInAssignment) {
|
TEST(file_test, move_from_temporary_in_assignment) {
|
||||||
int fd = 0xdead;
|
int fd = 0xdead;
|
||||||
file f;
|
file f;
|
||||||
f = OpenBufferedFile(fd);
|
f = open_buffered_file(fd);
|
||||||
EXPECT_EQ(fd, f.descriptor());
|
EXPECT_EQ(fd, f.descriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) {
|
TEST(file_test, move_from_temporary_in_assignment_closes_file) {
|
||||||
int fd = 0xdead;
|
int fd = 0xdead;
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
int old_fd = f.descriptor();
|
int old_fd = f.descriptor();
|
||||||
f = OpenBufferedFile(fd);
|
f = open_buffered_file(fd);
|
||||||
EXPECT_TRUE(isclosed(old_fd));
|
EXPECT_TRUE(isclosed(old_fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, CloseFileInDtor) {
|
TEST(file_test, close_file_in_dtor) {
|
||||||
int fd = 0;
|
int fd = 0;
|
||||||
{
|
{
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
|
@ -388,7 +420,7 @@ TEST(FileTest, CloseFileInDtor) {
|
||||||
EXPECT_TRUE(isclosed(fd));
|
EXPECT_TRUE(isclosed(fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, CloseErrorInDtor) {
|
TEST(file_test, close_error_in_dtor) {
|
||||||
std::unique_ptr<file> f(new file(open_file()));
|
std::unique_ptr<file> f(new file(open_file()));
|
||||||
EXPECT_WRITE(
|
EXPECT_WRITE(
|
||||||
stderr,
|
stderr,
|
||||||
|
@ -400,10 +432,10 @@ TEST(FileTest, CloseErrorInDtor) {
|
||||||
FMT_POSIX(close(f->descriptor()));
|
FMT_POSIX(close(f->descriptor()));
|
||||||
SUPPRESS_ASSERT(f.reset(nullptr));
|
SUPPRESS_ASSERT(f.reset(nullptr));
|
||||||
},
|
},
|
||||||
format_system_error(EBADF, "cannot close file") + "\n");
|
system_error_message(EBADF, "cannot close file") + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Close) {
|
TEST(file_test, close) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
int fd = f.descriptor();
|
int fd = f.descriptor();
|
||||||
f.close();
|
f.close();
|
||||||
|
@ -411,19 +443,19 @@ TEST(FileTest, Close) {
|
||||||
EXPECT_TRUE(isclosed(fd));
|
EXPECT_TRUE(isclosed(fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, CloseError) {
|
TEST(file_test, close_error) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
FMT_POSIX(close(f.descriptor()));
|
FMT_POSIX(close(f.descriptor()));
|
||||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
|
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
|
||||||
EXPECT_EQ(-1, f.descriptor());
|
EXPECT_EQ(-1, f.descriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Read) {
|
TEST(file_test, read) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
EXPECT_READ(f, FILE_CONTENT);
|
EXPECT_READ(f, file_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, ReadError) {
|
TEST(file_test, read_error) {
|
||||||
file f("test-file", file::WRONLY);
|
file f("test-file", file::WRONLY);
|
||||||
char buf;
|
char buf;
|
||||||
// We intentionally read from a file opened in the write-only mode to
|
// We intentionally read from a file opened in the write-only mode to
|
||||||
|
@ -431,7 +463,7 @@ TEST(FileTest, ReadError) {
|
||||||
EXPECT_SYSTEM_ERROR(f.read(&buf, 1), EBADF, "cannot read from file");
|
EXPECT_SYSTEM_ERROR(f.read(&buf, 1), EBADF, "cannot read from file");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Write) {
|
TEST(file_test, write) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
write(write_end, "test");
|
write(write_end, "test");
|
||||||
|
@ -439,61 +471,61 @@ TEST(FileTest, Write) {
|
||||||
EXPECT_READ(read_end, "test");
|
EXPECT_READ(read_end, "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, WriteError) {
|
TEST(file_test, write_error) {
|
||||||
file f("test-file", file::RDONLY);
|
file f("test-file", file::RDONLY);
|
||||||
// We intentionally write to a file opened in the read-only mode to
|
// We intentionally write to a file opened in the read-only mode to
|
||||||
// cause error.
|
// cause error.
|
||||||
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
|
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Dup) {
|
TEST(file_test, dup) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
file copy = file::dup(f.descriptor());
|
file copy = file::dup(f.descriptor());
|
||||||
EXPECT_NE(f.descriptor(), copy.descriptor());
|
EXPECT_NE(f.descriptor(), copy.descriptor());
|
||||||
EXPECT_EQ(FILE_CONTENT, read(copy, std::strlen(FILE_CONTENT)));
|
EXPECT_EQ(file_content, read(copy, std::strlen(file_content)));
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifndef __COVERITY__
|
# ifndef __COVERITY__
|
||||||
TEST(FileTest, DupError) {
|
TEST(file_test, dup_error) {
|
||||||
int value = -1;
|
int value = -1;
|
||||||
EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF,
|
EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF,
|
||||||
"cannot duplicate file descriptor -1");
|
"cannot duplicate file descriptor -1");
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
TEST(FileTest, Dup2) {
|
TEST(file_test, dup2) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
file copy = open_file();
|
file copy = open_file();
|
||||||
f.dup2(copy.descriptor());
|
f.dup2(copy.descriptor());
|
||||||
EXPECT_NE(f.descriptor(), copy.descriptor());
|
EXPECT_NE(f.descriptor(), copy.descriptor());
|
||||||
EXPECT_READ(copy, FILE_CONTENT);
|
EXPECT_READ(copy, file_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Dup2Error) {
|
TEST(file_test, dup2_error) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
EXPECT_SYSTEM_ERROR_NOASSERT(
|
EXPECT_SYSTEM_ERROR_NOASSERT(
|
||||||
f.dup2(-1), EBADF,
|
f.dup2(-1), EBADF,
|
||||||
fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor()));
|
fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Dup2NoExcept) {
|
TEST(file_test, dup2_noexcept) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
file copy = open_file();
|
file copy = open_file();
|
||||||
error_code ec;
|
std::error_code ec;
|
||||||
f.dup2(copy.descriptor(), ec);
|
f.dup2(copy.descriptor(), ec);
|
||||||
EXPECT_EQ(ec.get(), 0);
|
EXPECT_EQ(ec.value(), 0);
|
||||||
EXPECT_NE(f.descriptor(), copy.descriptor());
|
EXPECT_NE(f.descriptor(), copy.descriptor());
|
||||||
EXPECT_READ(copy, FILE_CONTENT);
|
EXPECT_READ(copy, file_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Dup2NoExceptError) {
|
TEST(file_test, dup2_noexcept_error) {
|
||||||
file f = open_file();
|
file f = open_file();
|
||||||
error_code ec;
|
std::error_code ec;
|
||||||
SUPPRESS_ASSERT(f.dup2(-1, ec));
|
SUPPRESS_ASSERT(f.dup2(-1, ec));
|
||||||
EXPECT_EQ(EBADF, ec.get());
|
EXPECT_EQ(EBADF, ec.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Pipe) {
|
TEST(file_test, pipe) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
EXPECT_NE(-1, read_end.descriptor());
|
EXPECT_NE(-1, read_end.descriptor());
|
||||||
|
@ -502,7 +534,7 @@ TEST(FileTest, Pipe) {
|
||||||
EXPECT_READ(read_end, "test");
|
EXPECT_READ(read_end, "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Fdopen) {
|
TEST(file_test, fdopen) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
int read_fd = read_end.descriptor();
|
int read_fd = read_end.descriptor();
|
||||||
|
@ -510,7 +542,7 @@ TEST(FileTest, Fdopen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef FMT_LOCALE
|
# ifdef FMT_LOCALE
|
||||||
TEST(LocaleTest, Strtod) {
|
TEST(locale_test, strtod) {
|
||||||
fmt::locale loc;
|
fmt::locale loc;
|
||||||
const char *start = "4.2", *ptr = start;
|
const char *start = "4.2", *ptr = start;
|
||||||
EXPECT_EQ(4.2, loc.strtod(ptr));
|
EXPECT_EQ(4.2, loc.strtod(ptr));
|
||||||
|
|
|
@ -5,17 +5,17 @@
|
||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
#define FMT_STRING_ALIAS 1
|
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
using fmt::runtime;
|
||||||
|
|
||||||
struct test {};
|
struct test {};
|
||||||
|
|
||||||
// Test that there is no issues with specializations when fmt/ostream.h is
|
// Test that there is no issues with specializations when fmt/ostream.h is
|
||||||
// included after fmt/format.h.
|
// included after fmt/format.h.
|
||||||
namespace fmt {
|
namespace fmt {
|
||||||
template <> struct formatter<test> : formatter<int> {
|
template <> struct formatter<test> : formatter<int> {
|
||||||
template <typename FormatContext>
|
auto format(const test&, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
typename FormatContext::iterator format(const test&, FormatContext& ctx) {
|
|
||||||
return formatter<int>::format(42, ctx);
|
return formatter<int>::format(42, ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,19 +24,17 @@ template <> struct formatter<test> : formatter<int> {
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "fmt/ostream.h"
|
#include "fmt/ostream.h"
|
||||||
#include "gmock.h"
|
#include "fmt/ranges.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
using fmt::format;
|
std::ostream& operator<<(std::ostream& os, const date& d) {
|
||||||
using fmt::format_error;
|
|
||||||
|
|
||||||
static std::ostream& operator<<(std::ostream& os, const Date& d) {
|
|
||||||
os << d.year() << '-' << d.month() << '-' << d.day();
|
os << d.year() << '-' << d.month() << '-' << d.day();
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::wostream& operator<<(std::wostream& os, const Date& d) {
|
std::wostream& operator<<(std::wostream& os, const date& d) {
|
||||||
os << d.year() << L'-' << d.month() << L'-' << d.day();
|
os << d.year() << L'-' << d.month() << L'-' << d.day();
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
@ -44,99 +42,63 @@ static std::wostream& operator<<(std::wostream& os, const Date& d) {
|
||||||
// Make sure that overloaded comma operators do no harm to is_streamable.
|
// Make sure that overloaded comma operators do no harm to is_streamable.
|
||||||
struct type_with_comma_op {};
|
struct type_with_comma_op {};
|
||||||
template <typename T> void operator,(type_with_comma_op, const T&);
|
template <typename T> void operator,(type_with_comma_op, const T&);
|
||||||
template <typename T> type_with_comma_op operator<<(T&, const Date&);
|
template <typename T> type_with_comma_op operator<<(T&, const date&);
|
||||||
|
|
||||||
enum streamable_enum {};
|
enum streamable_enum {};
|
||||||
static std::ostream& operator<<(std::ostream& os, streamable_enum) {
|
|
||||||
return os << "streamable_enum";
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::wostream& operator<<(std::wostream& os, streamable_enum) {
|
std::ostream& operator<<(std::ostream& os, streamable_enum) {
|
||||||
return os << L"streamable_enum";
|
return os << "streamable_enum";
|
||||||
}
|
}
|
||||||
|
|
||||||
enum unstreamable_enum {};
|
enum unstreamable_enum {};
|
||||||
|
|
||||||
TEST(OStreamTest, Enum) {
|
TEST(ostream_test, enum) {
|
||||||
EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum()));
|
EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum()));
|
||||||
EXPECT_EQ("0", fmt::format("{}", unstreamable_enum()));
|
EXPECT_EQ("0", fmt::format("{}", unstreamable_enum()));
|
||||||
EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
|
|
||||||
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct test_arg_formatter
|
TEST(ostream_test, format) {
|
||||||
: fmt::detail::arg_formatter<fmt::format_context::iterator, char> {
|
EXPECT_EQ("a string", fmt::format("{0}", test_string("a string")));
|
||||||
fmt::format_parse_context parse_ctx;
|
EXPECT_EQ("The date is 2012-12-9",
|
||||||
test_arg_formatter(fmt::format_context& ctx, fmt::format_specs& s)
|
fmt::format("The date is {0}", date(2012, 12, 9)));
|
||||||
: fmt::detail::arg_formatter<fmt::format_context::iterator, char>(
|
|
||||||
ctx, &parse_ctx, &s),
|
|
||||||
parse_ctx("") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(OStreamTest, CustomArg) {
|
|
||||||
fmt::memory_buffer buffer;
|
|
||||||
fmt::detail::buffer<char>& base = buffer;
|
|
||||||
fmt::format_context ctx(std::back_inserter(base), fmt::format_args());
|
|
||||||
fmt::format_specs spec;
|
|
||||||
test_arg_formatter af(ctx, spec);
|
|
||||||
fmt::visit_format_arg(
|
|
||||||
af, fmt::detail::make_arg<fmt::format_context>(streamable_enum()));
|
|
||||||
EXPECT_EQ("streamable_enum", std::string(buffer.data(), buffer.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, Format) {
|
TEST(ostream_test, format_specs) {
|
||||||
EXPECT_EQ("a string", format("{0}", TestString("a string")));
|
using fmt::format_error;
|
||||||
std::string s = format("The date is {0}", Date(2012, 12, 9));
|
EXPECT_EQ("def ", fmt::format("{0:<5}", test_string("def")));
|
||||||
EXPECT_EQ("The date is 2012-12-9", s);
|
EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def")));
|
||||||
Date date(2012, 12, 9);
|
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
|
||||||
EXPECT_EQ(L"The date is 2012-12-9",
|
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
|
||||||
format(L"The date is {0}", Date(2012, 12, 9)));
|
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), test_string()), format_error,
|
||||||
|
"format specifier requires numeric argument");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), test_string()), format_error,
|
||||||
|
"format specifier requires numeric argument");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), test_string()), format_error,
|
||||||
|
"format specifier requires numeric argument");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), test_string()), format_error,
|
||||||
|
"format specifier requires numeric argument");
|
||||||
|
EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), test_string()), format_error,
|
||||||
|
"format specifier requires numeric argument");
|
||||||
|
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
|
||||||
|
EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13));
|
||||||
|
EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test")));
|
||||||
|
EXPECT_EQ("te", fmt::format("{0:.{1}}", test_string("test"), 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, FormatSpecs) {
|
struct empty_test {};
|
||||||
EXPECT_EQ("def ", format("{0:<5}", TestString("def")));
|
std::ostream& operator<<(std::ostream& os, empty_test) { return os << ""; }
|
||||||
EXPECT_EQ(" def", format("{0:>5}", TestString("def")));
|
|
||||||
#if FMT_DEPRECATED_NUMERIC_ALIGN
|
TEST(ostream_test, empty_custom_output) {
|
||||||
EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), format_error,
|
EXPECT_EQ("", fmt::format("{}", empty_test()));
|
||||||
"format specifier requires numeric argument");
|
|
||||||
#endif
|
|
||||||
EXPECT_EQ(" def ", format("{0:^5}", TestString("def")));
|
|
||||||
EXPECT_EQ("def**", format("{0:*<5}", TestString("def")));
|
|
||||||
EXPECT_THROW_MSG(format("{0:+}", TestString()), format_error,
|
|
||||||
"format specifier requires numeric argument");
|
|
||||||
EXPECT_THROW_MSG(format("{0:-}", TestString()), format_error,
|
|
||||||
"format specifier requires numeric argument");
|
|
||||||
EXPECT_THROW_MSG(format("{0: }", TestString()), format_error,
|
|
||||||
"format specifier requires numeric argument");
|
|
||||||
EXPECT_THROW_MSG(format("{0:#}", TestString()), format_error,
|
|
||||||
"format specifier requires numeric argument");
|
|
||||||
EXPECT_THROW_MSG(format("{0:05}", TestString()), format_error,
|
|
||||||
"format specifier requires numeric argument");
|
|
||||||
EXPECT_EQ("test ", format("{0:13}", TestString("test")));
|
|
||||||
EXPECT_EQ("test ", format("{0:{1}}", TestString("test"), 13));
|
|
||||||
EXPECT_EQ("te", format("{0:.2}", TestString("test")));
|
|
||||||
EXPECT_EQ("te", format("{0:.{1}}", TestString("test"), 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EmptyTest {};
|
TEST(ostream_test, print) {
|
||||||
static std::ostream& operator<<(std::ostream& os, EmptyTest) {
|
|
||||||
return os << "";
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OStreamTest, EmptyCustomOutput) {
|
|
||||||
EXPECT_EQ("", fmt::format("{}", EmptyTest()));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OStreamTest, Print) {
|
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
fmt::print(os, "Don't {}!", "panic");
|
fmt::print(os, "Don't {}!", "panic");
|
||||||
EXPECT_EQ("Don't panic!", os.str());
|
EXPECT_EQ("Don't panic!", os.str());
|
||||||
std::wostringstream wos;
|
|
||||||
fmt::print(wos, L"Don't {}!", L"panic");
|
|
||||||
EXPECT_EQ(L"Don't panic!", wos.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, WriteToOStream) {
|
TEST(ostream_test, write_to_ostream) {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
const char* foo = "foo";
|
const char* foo = "foo";
|
||||||
|
@ -145,13 +107,14 @@ TEST(OStreamTest, WriteToOStream) {
|
||||||
EXPECT_EQ("foo", os.str());
|
EXPECT_EQ("foo", os.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, WriteToOStreamMaxSize) {
|
TEST(ostream_test, write_to_ostream_max_size) {
|
||||||
size_t max_size = fmt::detail::max_value<size_t>();
|
auto max_size = fmt::detail::max_value<size_t>();
|
||||||
std::streamsize max_streamsize = fmt::detail::max_value<std::streamsize>();
|
auto max_streamsize = fmt::detail::max_value<std::streamsize>();
|
||||||
if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return;
|
if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return;
|
||||||
|
|
||||||
struct test_buffer : fmt::detail::buffer<char> {
|
struct test_buffer final : fmt::detail::buffer<char> {
|
||||||
explicit test_buffer(size_t size) { resize(size); }
|
explicit test_buffer(size_t size)
|
||||||
|
: fmt::detail::buffer<char>(nullptr, size, size) {}
|
||||||
void grow(size_t) {}
|
void grow(size_t) {}
|
||||||
} buffer(max_size);
|
} buffer(max_size);
|
||||||
|
|
||||||
|
@ -164,12 +127,13 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
|
||||||
} streambuf;
|
} streambuf;
|
||||||
|
|
||||||
struct test_ostream : std::ostream {
|
struct test_ostream : std::ostream {
|
||||||
explicit test_ostream(mock_streambuf& buffer) : std::ostream(&buffer) {}
|
explicit test_ostream(mock_streambuf& output_buffer)
|
||||||
|
: std::ostream(&output_buffer) {}
|
||||||
} os(streambuf);
|
} os(streambuf);
|
||||||
|
|
||||||
testing::InSequence sequence;
|
testing::InSequence sequence;
|
||||||
const char* data = nullptr;
|
const char* data = nullptr;
|
||||||
typedef std::make_unsigned<std::streamsize>::type ustreamsize;
|
using ustreamsize = std::make_unsigned<std::streamsize>::type;
|
||||||
ustreamsize size = max_size;
|
ustreamsize size = max_size;
|
||||||
do {
|
do {
|
||||||
auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize));
|
auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize));
|
||||||
|
@ -181,68 +145,62 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
|
||||||
fmt::detail::write_buffer(os, buffer);
|
fmt::detail::write_buffer(os, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, Join) {
|
TEST(ostream_test, join) {
|
||||||
int v[3] = {1, 2, 3};
|
int v[3] = {1, 2, 3};
|
||||||
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", ")));
|
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ostream_test, join_fallback_formatter) {
|
||||||
|
auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
|
||||||
|
EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", ")));
|
||||||
|
}
|
||||||
|
|
||||||
#if FMT_USE_CONSTEXPR
|
#if FMT_USE_CONSTEXPR
|
||||||
TEST(OStreamTest, ConstexprString) {
|
TEST(ostream_test, constexpr_string) {
|
||||||
EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42")));
|
EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42")));
|
||||||
EXPECT_EQ("a string", format(FMT_STRING("{0}"), TestString("a string")));
|
EXPECT_EQ("a string", format(FMT_STRING("{0}"), test_string("a string")));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace fmt_test {
|
namespace fmt_test {
|
||||||
struct ABC {};
|
struct abc {};
|
||||||
|
|
||||||
template <typename Output> Output& operator<<(Output& out, ABC) {
|
template <typename Output> Output& operator<<(Output& out, abc) {
|
||||||
out << "ABC";
|
return out << "abc";
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
} // namespace fmt_test
|
} // namespace fmt_test
|
||||||
|
|
||||||
template <typename T> struct TestTemplate {};
|
template <typename T> struct test_template {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::ostream& operator<<(std::ostream& os, TestTemplate<T>) {
|
std::ostream& operator<<(std::ostream& os, test_template<T>) {
|
||||||
return os << 1;
|
return os << 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace fmt {
|
namespace fmt {
|
||||||
template <typename T> struct formatter<TestTemplate<T>> : formatter<int> {
|
template <typename T> struct formatter<test_template<T>> : formatter<int> {
|
||||||
template <typename FormatContext>
|
auto format(test_template<T>, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
typename FormatContext::iterator format(TestTemplate<T>, FormatContext& ctx) {
|
|
||||||
return formatter<int>::format(2, ctx);
|
return formatter<int>::format(2, ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace fmt
|
} // namespace fmt
|
||||||
|
|
||||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 407
|
TEST(ostream_test, template) {
|
||||||
TEST(OStreamTest, Template) {
|
EXPECT_EQ("2", fmt::format("{}", test_template<int>()));
|
||||||
EXPECT_EQ("2", fmt::format("{}", TestTemplate<int>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FormatTest, FormatToN) {
|
TEST(ostream_test, format_to_n) {
|
||||||
char buffer[4];
|
char buffer[4];
|
||||||
buffer[3] = 'x';
|
buffer[3] = 'x';
|
||||||
auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::ABC());
|
auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::abc());
|
||||||
EXPECT_EQ(3u, result.size);
|
EXPECT_EQ(3u, result.size);
|
||||||
EXPECT_EQ(buffer + 3, result.out);
|
EXPECT_EQ(buffer + 3, result.out);
|
||||||
EXPECT_EQ("ABCx", fmt::string_view(buffer, 4));
|
EXPECT_EQ("abcx", fmt::string_view(buffer, 4));
|
||||||
result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::ABC());
|
result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::abc());
|
||||||
EXPECT_EQ(5u, result.size);
|
EXPECT_EQ(5u, result.size);
|
||||||
EXPECT_EQ(buffer + 3, result.out);
|
EXPECT_EQ(buffer + 3, result.out);
|
||||||
EXPECT_EQ("xABx", fmt::string_view(buffer, 4));
|
EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FMT_USE_USER_DEFINED_LITERALS
|
|
||||||
TEST(FormatTest, UDL) {
|
|
||||||
using namespace fmt::literals;
|
|
||||||
EXPECT_EQ("{}"_format("test"), "test");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename T> struct convertible {
|
template <typename T> struct convertible {
|
||||||
T value;
|
T value;
|
||||||
|
@ -250,9 +208,8 @@ template <typename T> struct convertible {
|
||||||
operator T() const { return value; }
|
operator T() const { return value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(OStreamTest, DisableBuiltinOStreamOperators) {
|
TEST(ostream_test, disable_builtin_ostream_operators) {
|
||||||
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
|
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
|
||||||
EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42)));
|
|
||||||
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
|
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +227,7 @@ std::ostream& operator<<(std::ostream& os,
|
||||||
return os << "bar";
|
return os << "bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, FormatExplicitlyConvertibleToStringLike) {
|
TEST(ostream_test, format_explicitly_convertible_to_string_like) {
|
||||||
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
|
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,12 +243,23 @@ std::ostream& operator<<(std::ostream& os,
|
||||||
return os << "bar";
|
return os << "bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, FormatExplicitlyConvertibleToStdStringView) {
|
TEST(ostream_test, format_explicitly_convertible_to_std_string_view) {
|
||||||
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
|
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FMT_USE_STRING_VIEW
|
#endif // FMT_USE_STRING_VIEW
|
||||||
|
|
||||||
|
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) {
|
||||||
|
EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool()));
|
||||||
|
}
|
||||||
|
|
||||||
struct copyfmt_test {};
|
struct copyfmt_test {};
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, copyfmt_test) {
|
std::ostream& operator<<(std::ostream& os, copyfmt_test) {
|
||||||
|
@ -300,10 +268,15 @@ std::ostream& operator<<(std::ostream& os, copyfmt_test) {
|
||||||
return os << "foo";
|
return os << "foo";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, CopyFmt) {
|
TEST(ostream_test, copyfmt) {
|
||||||
EXPECT_EQ("foo", fmt::format("{}", copyfmt_test()));
|
EXPECT_EQ("foo", fmt::format("{}", copyfmt_test()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OStreamTest, CompileTimeString) {
|
TEST(ostream_test, to_string) {
|
||||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
|
EXPECT_EQ("abc", fmt::to_string(fmt_test::abc()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ostream_test, range) {
|
||||||
|
auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
|
||||||
|
EXPECT_EQ("[foo, bar]", fmt::format("{}", strs));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,20 +23,23 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
# undef max
|
# undef max
|
||||||
# undef ERROR
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
using fmt::buffered_file;
|
using fmt::buffered_file;
|
||||||
using fmt::error_code;
|
|
||||||
|
|
||||||
using testing::_;
|
using testing::_;
|
||||||
using testing::Return;
|
using testing::Return;
|
||||||
using testing::StrEq;
|
using testing::StrEq;
|
||||||
|
|
||||||
|
template <typename Mock> struct scoped_mock : testing::StrictMock<Mock> {
|
||||||
|
scoped_mock() { Mock::instance = this; }
|
||||||
|
~scoped_mock() { Mock::instance = nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int open_count;
|
int open_count;
|
||||||
int close_count;
|
int close_count;
|
||||||
|
@ -53,7 +56,7 @@ size_t read_nbyte;
|
||||||
size_t write_nbyte;
|
size_t write_nbyte;
|
||||||
bool sysconf_error;
|
bool sysconf_error;
|
||||||
|
|
||||||
enum { NONE, MAX_SIZE, ERROR } fstat_sim;
|
enum { none, max_size, error } fstat_sim;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#define EMULATE_EINTR(func, error_result) \
|
#define EMULATE_EINTR(func, error_result) \
|
||||||
|
@ -91,7 +94,7 @@ static off_t max_file_size() { return std::numeric_limits<off_t>::max(); }
|
||||||
|
|
||||||
int test::fstat(int fd, struct stat* buf) {
|
int test::fstat(int fd, struct stat* buf) {
|
||||||
int result = ::fstat(fd, buf);
|
int result = ::fstat(fd, buf);
|
||||||
if (fstat_sim == MAX_SIZE) buf->st_size = max_file_size();
|
if (fstat_sim == max_size) buf->st_size = max_file_size();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,11 +103,11 @@ int test::fstat(int fd, struct stat* buf) {
|
||||||
static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); }
|
static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); }
|
||||||
|
|
||||||
DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
|
DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
|
||||||
if (fstat_sim == ERROR) {
|
if (fstat_sim == error) {
|
||||||
SetLastError(ERROR_ACCESS_DENIED);
|
SetLastError(ERROR_ACCESS_DENIED);
|
||||||
return INVALID_FILE_SIZE;
|
return INVALID_FILE_SIZE;
|
||||||
}
|
}
|
||||||
if (fstat_sim == MAX_SIZE) {
|
if (fstat_sim == max_size) {
|
||||||
DWORD max = std::numeric_limits<DWORD>::max();
|
DWORD max = std::numeric_limits<DWORD>::max();
|
||||||
*lpFileSizeHigh = max >> 1;
|
*lpFileSizeHigh = max >> 1;
|
||||||
return max;
|
return max;
|
||||||
|
@ -194,15 +197,15 @@ int(test::fileno)(FILE* stream) {
|
||||||
# define EXPECT_EQ_POSIX(expected, actual)
|
# define EXPECT_EQ_POSIX(expected, actual)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void write_file(fmt::cstring_view filename, fmt::string_view content) {
|
#if FMT_USE_FCNTL
|
||||||
|
void write_file(fmt::cstring_view filename, fmt::string_view content) {
|
||||||
fmt::buffered_file f(filename, "w");
|
fmt::buffered_file f(filename, "w");
|
||||||
f.print("{}", content);
|
f.print("{}", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
|
||||||
using fmt::file;
|
using fmt::file;
|
||||||
|
|
||||||
TEST(UtilTest, GetPageSize) {
|
TEST(os_test, getpagesize) {
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
SYSTEM_INFO si = {};
|
SYSTEM_INFO si = {};
|
||||||
GetSystemInfo(&si);
|
GetSystemInfo(&si);
|
||||||
|
@ -216,7 +219,7 @@ TEST(UtilTest, GetPageSize) {
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, OpenRetry) {
|
TEST(file_test, open_retry) {
|
||||||
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,
|
||||||
|
@ -227,7 +230,7 @@ TEST(FileTest, OpenRetry) {
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, CloseNoRetryInDtor) {
|
TEST(file_test, close_no_retry_in_dtor) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
std::unique_ptr<file> f(new file(std::move(read_end)));
|
std::unique_ptr<file> f(new file(std::move(read_end)));
|
||||||
|
@ -240,11 +243,11 @@ TEST(FileTest, CloseNoRetryInDtor) {
|
||||||
saved_close_count = close_count;
|
saved_close_count = close_count;
|
||||||
close_count = 0;
|
close_count = 0;
|
||||||
},
|
},
|
||||||
format_system_error(EINTR, "cannot close file") + "\n");
|
system_error_message(EINTR, "cannot close file") + "\n");
|
||||||
EXPECT_EQ(2, saved_close_count);
|
EXPECT_EQ(2, saved_close_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, CloseNoRetry) {
|
TEST(file_test, close_no_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
close_count = 1;
|
close_count = 1;
|
||||||
|
@ -253,35 +256,39 @@ TEST(FileTest, CloseNoRetry) {
|
||||||
close_count = 0;
|
close_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Size) {
|
TEST(file_test, size) {
|
||||||
std::string content = "top secret, destroy before reading";
|
std::string content = "top secret, destroy before reading";
|
||||||
write_file("temp", content);
|
write_file("temp", content);
|
||||||
file f("temp", file::RDONLY);
|
file f("temp", file::RDONLY);
|
||||||
EXPECT_GE(f.size(), 0);
|
EXPECT_GE(f.size(), 0);
|
||||||
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
|
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
fmt::memory_buffer message;
|
auto error_code = std::error_code();
|
||||||
fmt::detail::format_windows_error(message, ERROR_ACCESS_DENIED,
|
fstat_sim = error;
|
||||||
"cannot get file size");
|
try {
|
||||||
fstat_sim = ERROR;
|
f.size();
|
||||||
EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message));
|
} catch (const std::system_error& e) {
|
||||||
fstat_sim = NONE;
|
error_code = e.code();
|
||||||
|
}
|
||||||
|
fstat_sim = none;
|
||||||
|
EXPECT_EQ(error_code,
|
||||||
|
std::error_code(ERROR_ACCESS_DENIED, fmt::system_category()));
|
||||||
# else
|
# else
|
||||||
f.close();
|
f.close();
|
||||||
EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
|
EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, MaxSize) {
|
TEST(file_test, max_size) {
|
||||||
write_file("temp", "");
|
write_file("temp", "");
|
||||||
file f("temp", file::RDONLY);
|
file f("temp", file::RDONLY);
|
||||||
fstat_sim = MAX_SIZE;
|
fstat_sim = max_size;
|
||||||
EXPECT_GE(f.size(), 0);
|
EXPECT_GE(f.size(), 0);
|
||||||
EXPECT_EQ(max_file_size(), f.size());
|
EXPECT_EQ(max_file_size(), f.size());
|
||||||
fstat_sim = NONE;
|
fstat_sim = none;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, ReadRetry) {
|
TEST(file_test, read_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
enum { SIZE = 4 };
|
enum { SIZE = 4 };
|
||||||
|
@ -294,7 +301,7 @@ TEST(FileTest, ReadRetry) {
|
||||||
EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
|
EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, WriteRetry) {
|
TEST(file_test, write_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
enum { SIZE = 4 };
|
enum { SIZE = 4 };
|
||||||
|
@ -312,7 +319,7 @@ TEST(FileTest, WriteRetry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
TEST(FileTest, ConvertReadCount) {
|
TEST(file_test, convert_read_count) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
char c;
|
char c;
|
||||||
|
@ -320,12 +327,12 @@ TEST(FileTest, ConvertReadCount) {
|
||||||
if (sizeof(unsigned) != sizeof(size_t)) ++size;
|
if (sizeof(unsigned) != sizeof(size_t)) ++size;
|
||||||
read_count = 1;
|
read_count = 1;
|
||||||
read_nbyte = 0;
|
read_nbyte = 0;
|
||||||
EXPECT_THROW(read_end.read(&c, size), fmt::system_error);
|
EXPECT_THROW(read_end.read(&c, size), std::system_error);
|
||||||
read_count = 0;
|
read_count = 0;
|
||||||
EXPECT_EQ(UINT_MAX, read_nbyte);
|
EXPECT_EQ(UINT_MAX, read_nbyte);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, ConvertWriteCount) {
|
TEST(file_test, convert_write_count) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
char c;
|
char c;
|
||||||
|
@ -333,13 +340,13 @@ TEST(FileTest, ConvertWriteCount) {
|
||||||
if (sizeof(unsigned) != sizeof(size_t)) ++size;
|
if (sizeof(unsigned) != sizeof(size_t)) ++size;
|
||||||
write_count = 1;
|
write_count = 1;
|
||||||
write_nbyte = 0;
|
write_nbyte = 0;
|
||||||
EXPECT_THROW(write_end.write(&c, size), fmt::system_error);
|
EXPECT_THROW(write_end.write(&c, size), std::system_error);
|
||||||
write_count = 0;
|
write_count = 0;
|
||||||
EXPECT_EQ(UINT_MAX, write_nbyte);
|
EXPECT_EQ(UINT_MAX, write_nbyte);
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
TEST(FileTest, DupNoRetry) {
|
TEST(file_test, dup_no_retry) {
|
||||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||||
dup_count = 1;
|
dup_count = 1;
|
||||||
EXPECT_SYSTEM_ERROR(
|
EXPECT_SYSTEM_ERROR(
|
||||||
|
@ -348,7 +355,7 @@ TEST(FileTest, DupNoRetry) {
|
||||||
dup_count = 0;
|
dup_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Dup2Retry) {
|
TEST(file_test, dup2_retry) {
|
||||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||||
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
|
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
|
||||||
EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
|
EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
|
||||||
|
@ -356,21 +363,21 @@ TEST(FileTest, Dup2Retry) {
|
||||||
f1.descriptor(), f2.descriptor()));
|
f1.descriptor(), f2.descriptor()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, Dup2NoExceptRetry) {
|
TEST(file_test, dup2_no_except_retry) {
|
||||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||||
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
|
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
|
||||||
error_code ec;
|
std::error_code ec;
|
||||||
dup2_count = 1;
|
dup2_count = 1;
|
||||||
f1.dup2(f2.descriptor(), ec);
|
f1.dup2(f2.descriptor(), ec);
|
||||||
# ifndef _WIN32
|
# ifndef _WIN32
|
||||||
EXPECT_EQ(4, dup2_count);
|
EXPECT_EQ(4, dup2_count);
|
||||||
# else
|
# else
|
||||||
EXPECT_EQ(EINTR, ec.get());
|
EXPECT_EQ(EINTR, ec.value());
|
||||||
# endif
|
# endif
|
||||||
dup2_count = 0;
|
dup2_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, PipeNoRetry) {
|
TEST(file_test, pipe_no_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
pipe_count = 1;
|
pipe_count = 1;
|
||||||
EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR,
|
EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR,
|
||||||
|
@ -378,7 +385,7 @@ TEST(FileTest, PipeNoRetry) {
|
||||||
pipe_count = 0;
|
pipe_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FileTest, FdopenNoRetry) {
|
TEST(file_test, fdopen_no_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
fdopen_count = 1;
|
fdopen_count = 1;
|
||||||
|
@ -387,7 +394,7 @@ TEST(FileTest, FdopenNoRetry) {
|
||||||
fdopen_count = 0;
|
fdopen_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, OpenRetry) {
|
TEST(buffered_file_test, open_retry) {
|
||||||
write_file("temp", "there must be something here");
|
write_file("temp", "there must be something here");
|
||||||
std::unique_ptr<buffered_file> f{nullptr};
|
std::unique_ptr<buffered_file> f{nullptr};
|
||||||
EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
|
EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
|
||||||
|
@ -399,7 +406,7 @@ TEST(BufferedFileTest, OpenRetry) {
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
TEST(buffered_file_test, close_no_retry_in_dtor) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
|
std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
|
||||||
|
@ -412,11 +419,11 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
||||||
saved_fclose_count = fclose_count;
|
saved_fclose_count = fclose_count;
|
||||||
fclose_count = 0;
|
fclose_count = 0;
|
||||||
},
|
},
|
||||||
format_system_error(EINTR, "cannot close file") + "\n");
|
system_error_message(EINTR, "cannot close file") + "\n");
|
||||||
EXPECT_EQ(2, saved_fclose_count);
|
EXPECT_EQ(2, saved_fclose_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, CloseNoRetry) {
|
TEST(buffered_file_test, close_no_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
buffered_file f = read_end.fdopen("r");
|
buffered_file f = read_end.fdopen("r");
|
||||||
|
@ -426,7 +433,7 @@ TEST(BufferedFileTest, CloseNoRetry) {
|
||||||
fclose_count = 0;
|
fclose_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BufferedFileTest, FilenoNoRetry) {
|
TEST(buffered_file_test, fileno_no_retry) {
|
||||||
file read_end, write_end;
|
file read_end, write_end;
|
||||||
file::pipe(read_end, write_end);
|
file::pipe(read_end, write_end);
|
||||||
buffered_file f = read_end.fdopen("r");
|
buffered_file f = read_end.fdopen("r");
|
||||||
|
@ -441,9 +448,9 @@ struct test_mock {
|
||||||
static test_mock* instance;
|
static test_mock* instance;
|
||||||
} * test_mock::instance;
|
} * test_mock::instance;
|
||||||
|
|
||||||
TEST(ScopedMock, Scope) {
|
TEST(scoped_mock, scope) {
|
||||||
{
|
{
|
||||||
ScopedMock<test_mock> mock;
|
scoped_mock<test_mock> mock;
|
||||||
EXPECT_EQ(&mock, test_mock::instance);
|
EXPECT_EQ(&mock, test_mock::instance);
|
||||||
test_mock& copy = mock;
|
test_mock& copy = mock;
|
||||||
static_cast<void>(copy);
|
static_cast<void>(copy);
|
||||||
|
@ -453,7 +460,7 @@ TEST(ScopedMock, Scope) {
|
||||||
|
|
||||||
#ifdef FMT_LOCALE
|
#ifdef FMT_LOCALE
|
||||||
|
|
||||||
typedef fmt::locale::type locale_type;
|
using locale_type = fmt::locale::type;
|
||||||
|
|
||||||
struct locale_mock {
|
struct locale_mock {
|
||||||
static locale_mock* instance;
|
static locale_mock* instance;
|
||||||
|
@ -490,7 +497,8 @@ double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||||
# pragma warning(pop)
|
# pragma warning(pop)
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# if defined(__THROW) && FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408
|
# if defined(__THROW) && \
|
||||||
|
((FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408) || defined(__e2k__))
|
||||||
# define FMT_LOCALE_THROW __THROW
|
# define FMT_LOCALE_THROW __THROW
|
||||||
# else
|
# else
|
||||||
# define FMT_LOCALE_THROW
|
# define FMT_LOCALE_THROW
|
||||||
|
@ -520,20 +528,20 @@ locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
|
||||||
return locale_mock::instance->newlocale(category_mask, locale, base);
|
return locale_mock::instance->newlocale(category_mask, locale, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LocaleTest, LocaleMock) {
|
TEST(locale_test, locale_mock) {
|
||||||
ScopedMock<locale_mock> mock;
|
scoped_mock<locale_mock> mock;
|
||||||
locale_type locale = reinterpret_cast<locale_type>(11);
|
auto locale = reinterpret_cast<locale_type>(11);
|
||||||
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
||||||
FMT_SYSTEM(newlocale(222, "foo", locale));
|
FMT_SYSTEM(newlocale(222, "foo", locale));
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
TEST(LocaleTest, Locale) {
|
TEST(locale_test, locale) {
|
||||||
# ifndef LC_NUMERIC_MASK
|
# ifndef LC_NUMERIC_MASK
|
||||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||||
# endif
|
# endif
|
||||||
ScopedMock<locale_mock> mock;
|
scoped_mock<locale_mock> mock;
|
||||||
locale_type impl = reinterpret_cast<locale_type>(42);
|
auto impl = reinterpret_cast<locale_type>(42);
|
||||||
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
|
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
|
||||||
.WillOnce(Return(impl));
|
.WillOnce(Return(impl));
|
||||||
EXPECT_CALL(mock, freelocale(impl));
|
EXPECT_CALL(mock, freelocale(impl));
|
||||||
|
@ -541,8 +549,8 @@ TEST(LocaleTest, Locale) {
|
||||||
EXPECT_EQ(impl, loc.get());
|
EXPECT_EQ(impl, loc.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LocaleTest, Strtod) {
|
TEST(locale_test, strtod) {
|
||||||
ScopedMock<locale_mock> mock;
|
scoped_mock<locale_mock> mock;
|
||||||
EXPECT_CALL(mock, newlocale(_, _, _))
|
EXPECT_CALL(mock, newlocale(_, _, _))
|
||||||
.WillOnce(Return(reinterpret_cast<locale_type>(42)));
|
.WillOnce(Return(reinterpret_cast<locale_type>(42)));
|
||||||
EXPECT_CALL(mock, freelocale(_));
|
EXPECT_CALL(mock, freelocale(_));
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "fmt/core.h"
|
#include "fmt/ostream.h"
|
||||||
|
#include "fmt/xchar.h"
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ using fmt::format;
|
||||||
using fmt::format_error;
|
using fmt::format_error;
|
||||||
using fmt::detail::max_value;
|
using fmt::detail::max_value;
|
||||||
|
|
||||||
const unsigned BIG_NUM = INT_MAX + 1u;
|
const unsigned big_num = INT_MAX + 1u;
|
||||||
|
|
||||||
// Makes format string argument positional.
|
// Makes format string argument positional.
|
||||||
static std::string make_positional(fmt::string_view format) {
|
static std::string make_positional(fmt::string_view format) {
|
||||||
|
@ -28,7 +29,7 @@ static std::string make_positional(fmt::string_view format) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::wstring make_positional(fmt::wstring_view format) {
|
static std::wstring make_positional(fmt::basic_string_view<wchar_t> format) {
|
||||||
std::wstring s(format.data(), format.size());
|
std::wstring s(format.data(), format.size());
|
||||||
s.replace(s.find(L'%'), 1, L"%1$");
|
s.replace(s.find(L'%'), 1, L"%1$");
|
||||||
return s;
|
return s;
|
||||||
|
@ -41,7 +42,8 @@ std::string test_sprintf(fmt::string_view format, const Args&... args) {
|
||||||
return fmt::sprintf(format, args...);
|
return fmt::sprintf(format, args...);
|
||||||
}
|
}
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
std::wstring test_sprintf(fmt::wstring_view format, const Args&... args) {
|
std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
|
||||||
|
const Args&... args) {
|
||||||
return fmt::sprintf(format, args...);
|
return fmt::sprintf(format, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,12 +52,12 @@ std::wstring test_sprintf(fmt::wstring_view format, const Args&... args) {
|
||||||
<< "format: " << format; \
|
<< "format: " << format; \
|
||||||
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
|
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
|
||||||
|
|
||||||
TEST(PrintfTest, NoArgs) {
|
TEST(printf_test, no_args) {
|
||||||
EXPECT_EQ("test", test_sprintf("test"));
|
EXPECT_EQ("test", test_sprintf("test"));
|
||||||
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
|
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Escape) {
|
TEST(printf_test, escape) {
|
||||||
EXPECT_EQ("%", test_sprintf("%%"));
|
EXPECT_EQ("%", test_sprintf("%%"));
|
||||||
EXPECT_EQ("before %", test_sprintf("before %%"));
|
EXPECT_EQ("before %", test_sprintf("before %%"));
|
||||||
EXPECT_EQ("% after", test_sprintf("%% after"));
|
EXPECT_EQ("% after", test_sprintf("%% after"));
|
||||||
|
@ -68,7 +70,7 @@ TEST(PrintfTest, Escape) {
|
||||||
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
|
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, PositionalArgs) {
|
TEST(printf_test, positional_args) {
|
||||||
EXPECT_EQ("42", test_sprintf("%1$d", 42));
|
EXPECT_EQ("42", test_sprintf("%1$d", 42));
|
||||||
EXPECT_EQ("before 42", test_sprintf("before %1$d", 42));
|
EXPECT_EQ("before 42", test_sprintf("before %1$d", 42));
|
||||||
EXPECT_EQ("42 after", test_sprintf("%1$d after", 42));
|
EXPECT_EQ("42 after", test_sprintf("%1$d after", 42));
|
||||||
|
@ -78,40 +80,40 @@ TEST(PrintfTest, PositionalArgs) {
|
||||||
EXPECT_EQ("abracadabra", test_sprintf("%1$s%2$s%1$s", "abra", "cad"));
|
EXPECT_EQ("abracadabra", test_sprintf("%1$s%2$s%1$s", "abra", "cad"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, AutomaticArgIndexing) {
|
TEST(printf_test, automatic_arg_indexing) {
|
||||||
EXPECT_EQ("abc", test_sprintf("%c%c%c", 'a', 'b', 'c'));
|
EXPECT_EQ("abc", test_sprintf("%c%c%c", 'a', 'b', 'c'));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, NumberIsTooBigInArgIndex) {
|
TEST(printf_test, number_is_too_big_in_arg_index) {
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%{}$", BIG_NUM)), format_error,
|
EXPECT_THROW_MSG(test_sprintf(format("%{}$", big_num)), format_error,
|
||||||
"number is too big");
|
"argument not found");
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM)), format_error,
|
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num)), format_error,
|
||||||
"number is too big");
|
"argument not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, SwitchArgIndexing) {
|
TEST(printf_test, switch_arg_indexing) {
|
||||||
EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error,
|
||||||
"cannot switch from manual to automatic argument indexing");
|
"cannot switch from manual to automatic argument indexing");
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", BIG_NUM), 1, 2),
|
EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2),
|
||||||
format_error, "number is too big");
|
format_error, "number is too big");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error,
|
||||||
"cannot switch from manual to automatic argument indexing");
|
"cannot switch from manual to automatic argument indexing");
|
||||||
|
|
||||||
EXPECT_THROW_MSG(test_sprintf("%d%1$", 1, 2), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%d%1$", 1, 2), format_error,
|
||||||
"cannot switch from automatic to manual argument indexing");
|
"cannot switch from automatic to manual argument indexing");
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%d%{}$d", BIG_NUM), 1, 2), format_error,
|
EXPECT_THROW_MSG(test_sprintf(format("%d%{}$d", big_num), 1, 2), format_error,
|
||||||
"number is too big");
|
"cannot switch from automatic to manual argument indexing");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%d%1$d", 1, 2), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%d%1$d", 1, 2), format_error,
|
||||||
"cannot switch from automatic to manual argument indexing");
|
"cannot switch from automatic to manual argument indexing");
|
||||||
|
|
||||||
// Indexing errors override width errors.
|
// Indexing errors override width errors.
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%d%1${}d", BIG_NUM), 1, 2),
|
EXPECT_THROW_MSG(test_sprintf(format("%d%1${}d", big_num), 1, 2),
|
||||||
format_error, "number is too big");
|
format_error, "number is too big");
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", BIG_NUM), 1, 2),
|
EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2),
|
||||||
format_error, "number is too big");
|
format_error, "number is too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, InvalidArgIndex) {
|
TEST(printf_test, invalid_arg_index) {
|
||||||
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
|
||||||
"argument not found");
|
"argument not found");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
|
||||||
|
@ -120,16 +122,16 @@ TEST(PrintfTest, InvalidArgIndex) {
|
||||||
"argument not found");
|
"argument not found");
|
||||||
|
|
||||||
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found");
|
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found");
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM), 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num), 42), format_error,
|
||||||
"number is too big");
|
"argument not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, DefaultAlignRight) {
|
TEST(printf_test, default_align_right) {
|
||||||
EXPECT_PRINTF(" 42", "%5d", 42);
|
EXPECT_PRINTF(" 42", "%5d", 42);
|
||||||
EXPECT_PRINTF(" abc", "%5s", "abc");
|
EXPECT_PRINTF(" abc", "%5s", "abc");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, ZeroFlag) {
|
TEST(printf_test, zero_flag) {
|
||||||
EXPECT_PRINTF("00042", "%05d", 42);
|
EXPECT_PRINTF("00042", "%05d", 42);
|
||||||
EXPECT_PRINTF("-0042", "%05d", -42);
|
EXPECT_PRINTF("-0042", "%05d", -42);
|
||||||
|
|
||||||
|
@ -146,7 +148,7 @@ TEST(PrintfTest, ZeroFlag) {
|
||||||
EXPECT_PRINTF(" x", "%05c", 'x');
|
EXPECT_PRINTF(" x", "%05c", 'x');
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, PlusFlag) {
|
TEST(printf_test, plus_flag) {
|
||||||
EXPECT_PRINTF("+42", "%+d", 42);
|
EXPECT_PRINTF("+42", "%+d", 42);
|
||||||
EXPECT_PRINTF("-42", "%+d", -42);
|
EXPECT_PRINTF("-42", "%+d", -42);
|
||||||
EXPECT_PRINTF("+0042", "%+05d", 42);
|
EXPECT_PRINTF("+0042", "%+05d", 42);
|
||||||
|
@ -168,7 +170,7 @@ TEST(PrintfTest, PlusFlag) {
|
||||||
EXPECT_PRINTF("x", "% +c", 'x');
|
EXPECT_PRINTF("x", "% +c", 'x');
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, MinusFlag) {
|
TEST(printf_test, minus_flag) {
|
||||||
EXPECT_PRINTF("abc ", "%-5s", "abc");
|
EXPECT_PRINTF("abc ", "%-5s", "abc");
|
||||||
EXPECT_PRINTF("abc ", "%0--5s", "abc");
|
EXPECT_PRINTF("abc ", "%0--5s", "abc");
|
||||||
|
|
||||||
|
@ -188,7 +190,7 @@ TEST(PrintfTest, MinusFlag) {
|
||||||
EXPECT_PRINTF(" 42", "%- d", 42);
|
EXPECT_PRINTF(" 42", "%- d", 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, SpaceFlag) {
|
TEST(printf_test, space_flag) {
|
||||||
EXPECT_PRINTF(" 42", "% d", 42);
|
EXPECT_PRINTF(" 42", "% d", 42);
|
||||||
EXPECT_PRINTF("-42", "% d", -42);
|
EXPECT_PRINTF("-42", "% d", -42);
|
||||||
EXPECT_PRINTF(" 0042", "% 05d", 42);
|
EXPECT_PRINTF(" 0042", "% 05d", 42);
|
||||||
|
@ -198,7 +200,7 @@ TEST(PrintfTest, SpaceFlag) {
|
||||||
EXPECT_PRINTF("x", "% c", 'x');
|
EXPECT_PRINTF("x", "% c", 'x');
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, HashFlag) {
|
TEST(printf_test, hash_flag) {
|
||||||
EXPECT_PRINTF("042", "%#o", 042);
|
EXPECT_PRINTF("042", "%#o", 042);
|
||||||
EXPECT_PRINTF(fmt::format("0{:o}", static_cast<unsigned>(-042)), "%#o", -042);
|
EXPECT_PRINTF(fmt::format("0{:o}", static_cast<unsigned>(-042)), "%#o", -042);
|
||||||
EXPECT_PRINTF("0", "%#o", 0);
|
EXPECT_PRINTF("0", "%#o", 0);
|
||||||
|
@ -215,7 +217,7 @@ TEST(PrintfTest, HashFlag) {
|
||||||
EXPECT_PRINTF("-42.000000", "%#f", -42.0);
|
EXPECT_PRINTF("-42.000000", "%#f", -42.0);
|
||||||
EXPECT_PRINTF("-42.000000", "%#F", -42.0);
|
EXPECT_PRINTF("-42.000000", "%#F", -42.0);
|
||||||
|
|
||||||
char buffer[BUFFER_SIZE];
|
char buffer[256];
|
||||||
safe_sprintf(buffer, "%#e", -42.0);
|
safe_sprintf(buffer, "%#e", -42.0);
|
||||||
EXPECT_PRINTF(buffer, "%#e", -42.0);
|
EXPECT_PRINTF(buffer, "%#e", -42.0);
|
||||||
safe_sprintf(buffer, "%#E", -42.0);
|
safe_sprintf(buffer, "%#E", -42.0);
|
||||||
|
@ -233,30 +235,30 @@ TEST(PrintfTest, HashFlag) {
|
||||||
EXPECT_PRINTF("x", "%#c", 'x');
|
EXPECT_PRINTF("x", "%#c", 'x');
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Width) {
|
TEST(printf_test, width) {
|
||||||
EXPECT_PRINTF(" abc", "%5s", "abc");
|
EXPECT_PRINTF(" abc", "%5s", "abc");
|
||||||
|
|
||||||
// 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 type 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");
|
||||||
EXPECT_THROW_MSG(test_sprintf(format("%1${}d", BIG_NUM), 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf(format("%1${}d", big_num), 42), format_error,
|
||||||
"number is too big");
|
"number is too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, DynamicWidth) {
|
TEST(printf_test, dynamic_width) {
|
||||||
EXPECT_EQ(" 42", test_sprintf("%*d", 5, 42));
|
EXPECT_EQ(" 42", test_sprintf("%*d", 5, 42));
|
||||||
EXPECT_EQ("42 ", test_sprintf("%*d", -5, 42));
|
EXPECT_EQ("42 ", test_sprintf("%*d", -5, 42));
|
||||||
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
|
||||||
"width is not integer");
|
"width is not integer");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found");
|
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%*d", BIG_NUM, 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%*d", big_num, 42), format_error,
|
||||||
"number is too big");
|
"number is too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, IntPrecision) {
|
TEST(printf_test, int_precision) {
|
||||||
EXPECT_PRINTF("00042", "%.5d", 42);
|
EXPECT_PRINTF("00042", "%.5d", 42);
|
||||||
EXPECT_PRINTF("-00042", "%.5d", -42);
|
EXPECT_PRINTF("-00042", "%.5d", -42);
|
||||||
EXPECT_PRINTF("00042", "%.5x", 0x42);
|
EXPECT_PRINTF("00042", "%.5x", 0x42);
|
||||||
|
@ -277,8 +279,8 @@ TEST(PrintfTest, IntPrecision) {
|
||||||
EXPECT_PRINTF("00042 ", "%-#10.5o", 042);
|
EXPECT_PRINTF("00042 ", "%-#10.5o", 042);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, FloatPrecision) {
|
TEST(printf_test, float_precision) {
|
||||||
char buffer[BUFFER_SIZE];
|
char buffer[256];
|
||||||
safe_sprintf(buffer, "%.3e", 1234.5678);
|
safe_sprintf(buffer, "%.3e", 1234.5678);
|
||||||
EXPECT_PRINTF(buffer, "%.3e", 1234.5678);
|
EXPECT_PRINTF(buffer, "%.3e", 1234.5678);
|
||||||
EXPECT_PRINTF("1234.568", "%.3f", 1234.5678);
|
EXPECT_PRINTF("1234.568", "%.3f", 1234.5678);
|
||||||
|
@ -287,22 +289,22 @@ TEST(PrintfTest, FloatPrecision) {
|
||||||
EXPECT_PRINTF(buffer, "%.3a", 1234.5678);
|
EXPECT_PRINTF(buffer, "%.3a", 1234.5678);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, StringPrecision) {
|
TEST(printf_test, string_precision) {
|
||||||
char test[] = {'H', 'e', 'l', 'l', 'o'};
|
char test[] = {'H', 'e', 'l', 'l', 'o'};
|
||||||
EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell");
|
EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, IgnorePrecisionForNonNumericArg) {
|
TEST(printf_test, ignore_precision_for_non_numeric_arg) {
|
||||||
EXPECT_PRINTF("abc", "%.5s", "abc");
|
EXPECT_PRINTF("abc", "%.5s", "abc");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, DynamicPrecision) {
|
TEST(printf_test, dynamic_precision) {
|
||||||
EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42));
|
EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42));
|
||||||
EXPECT_EQ("42", test_sprintf("%.*d", -5, 42));
|
EXPECT_EQ("42", test_sprintf("%.*d", -5, 42));
|
||||||
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
|
||||||
"precision is not integer");
|
"precision is not integer");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found");
|
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found");
|
||||||
EXPECT_THROW_MSG(test_sprintf("%.*d", BIG_NUM, 42), format_error,
|
EXPECT_THROW_MSG(test_sprintf("%.*d", big_num, 42), format_error,
|
||||||
"number is too big");
|
"number is too big");
|
||||||
if (sizeof(long long) != sizeof(int)) {
|
if (sizeof(long long) != sizeof(int)) {
|
||||||
long long prec = static_cast<long long>(INT_MIN) - 1;
|
long long prec = static_cast<long long>(INT_MIN) - 1;
|
||||||
|
@ -325,7 +327,7 @@ SPECIALIZE_MAKE_SIGNED(unsigned long long, long long);
|
||||||
|
|
||||||
// Test length format specifier ``length_spec``.
|
// Test length format specifier ``length_spec``.
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
void TestLength(const char* length_spec, U value) {
|
void test_length(const char* length_spec, U value) {
|
||||||
long long signed_value = 0;
|
long long signed_value = 0;
|
||||||
unsigned long long unsigned_value = 0;
|
unsigned long long unsigned_value = 0;
|
||||||
// Apply integer promotion to the argument.
|
// Apply integer promotion to the argument.
|
||||||
|
@ -364,54 +366,54 @@ void TestLength(const char* length_spec, U value) {
|
||||||
EXPECT_PRINTF(os.str(), fmt::format("%{}X", length_spec), value);
|
EXPECT_PRINTF(os.str(), fmt::format("%{}X", length_spec), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void TestLength(const char* length_spec) {
|
template <typename T> void test_length(const char* length_spec) {
|
||||||
T min = std::numeric_limits<T>::min(), max = max_value<T>();
|
T min = std::numeric_limits<T>::min(), max = max_value<T>();
|
||||||
TestLength<T>(length_spec, 42);
|
test_length<T>(length_spec, 42);
|
||||||
TestLength<T>(length_spec, -42);
|
test_length<T>(length_spec, -42);
|
||||||
TestLength<T>(length_spec, min);
|
test_length<T>(length_spec, min);
|
||||||
TestLength<T>(length_spec, max);
|
test_length<T>(length_spec, max);
|
||||||
long long long_long_min = std::numeric_limits<long long>::min();
|
long long long_long_min = std::numeric_limits<long long>::min();
|
||||||
if (static_cast<long long>(min) > long_long_min)
|
if (static_cast<long long>(min) > long_long_min)
|
||||||
TestLength<T>(length_spec, static_cast<long long>(min) - 1);
|
test_length<T>(length_spec, static_cast<long long>(min) - 1);
|
||||||
unsigned long long long_long_max = max_value<long long>();
|
unsigned long long long_long_max = max_value<long long>();
|
||||||
if (static_cast<unsigned long long>(max) < long_long_max)
|
if (static_cast<unsigned long long>(max) < long_long_max)
|
||||||
TestLength<T>(length_spec, static_cast<long long>(max) + 1);
|
test_length<T>(length_spec, static_cast<long long>(max) + 1);
|
||||||
TestLength<T>(length_spec, std::numeric_limits<short>::min());
|
test_length<T>(length_spec, std::numeric_limits<short>::min());
|
||||||
TestLength<T>(length_spec, max_value<unsigned short>());
|
test_length<T>(length_spec, max_value<unsigned short>());
|
||||||
TestLength<T>(length_spec, std::numeric_limits<int>::min());
|
test_length<T>(length_spec, std::numeric_limits<int>::min());
|
||||||
TestLength<T>(length_spec, max_value<int>());
|
test_length<T>(length_spec, max_value<int>());
|
||||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::min());
|
test_length<T>(length_spec, std::numeric_limits<unsigned>::min());
|
||||||
TestLength<T>(length_spec, max_value<unsigned>());
|
test_length<T>(length_spec, max_value<unsigned>());
|
||||||
TestLength<T>(length_spec, std::numeric_limits<long long>::min());
|
test_length<T>(length_spec, std::numeric_limits<long long>::min());
|
||||||
TestLength<T>(length_spec, max_value<long long>());
|
test_length<T>(length_spec, max_value<long long>());
|
||||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::min());
|
test_length<T>(length_spec, std::numeric_limits<unsigned long long>::min());
|
||||||
TestLength<T>(length_spec, max_value<unsigned long long>());
|
test_length<T>(length_spec, max_value<unsigned long long>());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Length) {
|
TEST(printf_test, length) {
|
||||||
TestLength<char>("hh");
|
test_length<char>("hh");
|
||||||
TestLength<signed char>("hh");
|
test_length<signed char>("hh");
|
||||||
TestLength<unsigned char>("hh");
|
test_length<unsigned char>("hh");
|
||||||
TestLength<short>("h");
|
test_length<short>("h");
|
||||||
TestLength<unsigned short>("h");
|
test_length<unsigned short>("h");
|
||||||
TestLength<long>("l");
|
test_length<long>("l");
|
||||||
TestLength<unsigned long>("l");
|
test_length<unsigned long>("l");
|
||||||
TestLength<long long>("ll");
|
test_length<long long>("ll");
|
||||||
TestLength<unsigned long long>("ll");
|
test_length<unsigned long long>("ll");
|
||||||
TestLength<intmax_t>("j");
|
test_length<intmax_t>("j");
|
||||||
TestLength<size_t>("z");
|
test_length<size_t>("z");
|
||||||
TestLength<std::ptrdiff_t>("t");
|
test_length<std::ptrdiff_t>("t");
|
||||||
long double max = max_value<long double>();
|
long double max = max_value<long double>();
|
||||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
|
EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
|
||||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Bool) {
|
TEST(printf_test, bool) {
|
||||||
EXPECT_PRINTF("1", "%d", true);
|
EXPECT_PRINTF("1", "%d", true);
|
||||||
EXPECT_PRINTF("true", "%s", true);
|
EXPECT_PRINTF("true", "%s", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Int) {
|
TEST(printf_test, int) {
|
||||||
EXPECT_PRINTF("-42", "%d", -42);
|
EXPECT_PRINTF("-42", "%d", -42);
|
||||||
EXPECT_PRINTF("-42", "%i", -42);
|
EXPECT_PRINTF("-42", "%i", -42);
|
||||||
unsigned u = 0 - 42u;
|
unsigned u = 0 - 42u;
|
||||||
|
@ -421,20 +423,20 @@ TEST(PrintfTest, Int) {
|
||||||
EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
|
EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, long_long) {
|
TEST(printf_test, long_long) {
|
||||||
// fmt::printf allows passing long long arguments to %d without length
|
// fmt::printf allows passing long long arguments to %d without length
|
||||||
// specifiers.
|
// specifiers.
|
||||||
long long max = max_value<long long>();
|
long long max = max_value<long long>();
|
||||||
EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
|
EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Float) {
|
TEST(printf_test, float) {
|
||||||
EXPECT_PRINTF("392.650000", "%f", 392.65);
|
EXPECT_PRINTF("392.650000", "%f", 392.65);
|
||||||
EXPECT_PRINTF("392.65", "%.2f", 392.65);
|
EXPECT_PRINTF("392.65", "%.2f", 392.65);
|
||||||
EXPECT_PRINTF("392.6", "%.1f", 392.65);
|
EXPECT_PRINTF("392.6", "%.1f", 392.65);
|
||||||
EXPECT_PRINTF("393", "%.f", 392.65);
|
EXPECT_PRINTF("393", "%.f", 392.65);
|
||||||
EXPECT_PRINTF("392.650000", "%F", 392.65);
|
EXPECT_PRINTF("392.650000", "%F", 392.65);
|
||||||
char buffer[BUFFER_SIZE];
|
char buffer[256];
|
||||||
safe_sprintf(buffer, "%e", 392.65);
|
safe_sprintf(buffer, "%e", 392.65);
|
||||||
EXPECT_PRINTF(buffer, "%e", 392.65);
|
EXPECT_PRINTF(buffer, "%e", 392.65);
|
||||||
safe_sprintf(buffer, "%E", 392.65);
|
safe_sprintf(buffer, "%E", 392.65);
|
||||||
|
@ -450,7 +452,7 @@ TEST(PrintfTest, Float) {
|
||||||
EXPECT_EQ(buffer, format("{:A}", -392.65));
|
EXPECT_EQ(buffer, format("{:A}", -392.65));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Inf) {
|
TEST(printf_test, inf) {
|
||||||
double inf = std::numeric_limits<double>::infinity();
|
double inf = std::numeric_limits<double>::infinity();
|
||||||
for (const char* type = "fega"; *type; ++type) {
|
for (const char* type = "fega"; *type; ++type) {
|
||||||
EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf);
|
EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf);
|
||||||
|
@ -459,7 +461,7 @@ TEST(PrintfTest, Inf) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Char) {
|
TEST(printf_test, char) {
|
||||||
EXPECT_PRINTF("x", "%c", 'x');
|
EXPECT_PRINTF("x", "%c", 'x');
|
||||||
int max = max_value<int>();
|
int max = max_value<int>();
|
||||||
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
||||||
|
@ -468,7 +470,7 @@ TEST(PrintfTest, Char) {
|
||||||
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
|
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, String) {
|
TEST(printf_test, string) {
|
||||||
EXPECT_PRINTF("abc", "%s", "abc");
|
EXPECT_PRINTF("abc", "%s", "abc");
|
||||||
const char* null_str = nullptr;
|
const char* null_str = nullptr;
|
||||||
EXPECT_PRINTF("(null)", "%s", null_str);
|
EXPECT_PRINTF("(null)", "%s", null_str);
|
||||||
|
@ -479,13 +481,13 @@ TEST(PrintfTest, String) {
|
||||||
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
|
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, UCharString) {
|
TEST(printf_test, uchar_string) {
|
||||||
unsigned char str[] = "test";
|
unsigned char str[] = "test";
|
||||||
unsigned char* pstr = str;
|
unsigned char* pstr = str;
|
||||||
EXPECT_EQ("test", fmt::sprintf("%s", pstr));
|
EXPECT_EQ("test", fmt::sprintf("%s", pstr));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Pointer) {
|
TEST(printf_test, pointer) {
|
||||||
int n;
|
int n;
|
||||||
void* p = &n;
|
void* p = &n;
|
||||||
EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
|
EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
|
||||||
|
@ -508,20 +510,16 @@ TEST(PrintfTest, Pointer) {
|
||||||
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
|
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, Location) {
|
|
||||||
// TODO: test %n
|
|
||||||
}
|
|
||||||
|
|
||||||
enum test_enum { answer = 42 };
|
enum test_enum { answer = 42 };
|
||||||
|
|
||||||
TEST(PrintfTest, Enum) {
|
TEST(printf_test, enum) {
|
||||||
EXPECT_PRINTF("42", "%d", answer);
|
EXPECT_PRINTF("42", "%d", answer);
|
||||||
volatile test_enum volatile_enum = answer;
|
volatile test_enum volatile_enum = answer;
|
||||||
EXPECT_PRINTF("42", "%d", volatile_enum);
|
EXPECT_PRINTF("42", "%d", volatile_enum);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
TEST(PrintfTest, Examples) {
|
TEST(printf_test, examples) {
|
||||||
const char* weekday = "Thursday";
|
const char* weekday = "Thursday";
|
||||||
const char* month = "August";
|
const char* month = "August";
|
||||||
int day = 21;
|
int day = 21;
|
||||||
|
@ -529,7 +527,7 @@ TEST(PrintfTest, Examples) {
|
||||||
"Thursday, 21 August");
|
"Thursday, 21 August");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, PrintfError) {
|
TEST(printf_test, printf_error) {
|
||||||
fmt::file read_end, write_end;
|
fmt::file read_end, write_end;
|
||||||
fmt::file::pipe(read_end, write_end);
|
fmt::file::pipe(read_end, write_end);
|
||||||
int result = fmt::fprintf(read_end.fdopen("r").get(), "test");
|
int result = fmt::fprintf(read_end.fdopen("r").get(), "test");
|
||||||
|
@ -537,26 +535,20 @@ TEST(PrintfTest, PrintfError) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(PrintfTest, WideString) { EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); }
|
TEST(printf_test, wide_string) {
|
||||||
|
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
|
||||||
TEST(PrintfTest, PrintfCustom) {
|
|
||||||
EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, OStream) {
|
TEST(printf_test, printf_custom) {
|
||||||
std::ostringstream os;
|
EXPECT_EQ("abc", test_sprintf("%s", test_string("abc")));
|
||||||
int ret = fmt::fprintf(os, "Don't %s!", "panic");
|
|
||||||
EXPECT_EQ("Don't panic!", os.str());
|
|
||||||
EXPECT_EQ(12, ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, VPrintf) {
|
TEST(printf_test, vprintf) {
|
||||||
fmt::format_arg_store<fmt::printf_context, int> as{42};
|
fmt::format_arg_store<fmt::printf_context, int> as{42};
|
||||||
fmt::basic_format_args<fmt::printf_context> args(as);
|
fmt::basic_format_args<fmt::printf_context> args(as);
|
||||||
EXPECT_EQ(fmt::vsprintf("%d", args), "42");
|
EXPECT_EQ(fmt::vsprintf("%d", args), "42");
|
||||||
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
|
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
|
||||||
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
|
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
|
||||||
EXPECT_WRITE(stdout, fmt::vfprintf(std::cout, "%d", args), "42");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
|
@ -564,15 +556,15 @@ void check_format_string_regression(fmt::string_view s, const Args&... args) {
|
||||||
fmt::sprintf(s, args...);
|
fmt::sprintf(s, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, CheckFormatStringRegression) {
|
TEST(printf_test, check_format_string_regression) {
|
||||||
check_format_string_regression("%c%s", 'x', "");
|
check_format_string_regression("%c%s", 'x', "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, FixedLargeExponent) {
|
TEST(printf_test, fixed_large_exponent) {
|
||||||
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
|
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, VSPrintfMakeArgsExample) {
|
TEST(printf_test, vsprintf_make_args_example) {
|
||||||
fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
|
fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
|
||||||
"something"};
|
"something"};
|
||||||
fmt::basic_format_args<fmt::printf_context> args(as);
|
fmt::basic_format_args<fmt::printf_context> args(as);
|
||||||
|
@ -581,15 +573,12 @@ TEST(PrintfTest, VSPrintfMakeArgsExample) {
|
||||||
fmt::basic_format_args<fmt::printf_context> args2(as2);
|
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("[%d] %s happened", args2));
|
||||||
// The older gcc versions can't cast the return value.
|
|
||||||
#if !defined(__GNUC__) || (__GNUC__ > 4)
|
|
||||||
EXPECT_EQ("[42] something happened",
|
EXPECT_EQ("[42] something happened",
|
||||||
fmt::vsprintf("[%d] %s happened",
|
fmt::vsprintf("[%d] %s happened",
|
||||||
{fmt::make_printf_args(42, "something")}));
|
{fmt::make_printf_args(42, "something")}));
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PrintfTest, VSPrintfMakeWArgsExample) {
|
TEST(printf_test, vsprintf_make_wargs_example) {
|
||||||
fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
|
fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
|
||||||
42, L"something"};
|
42, L"something"};
|
||||||
fmt::basic_format_args<fmt::wprintf_context> args(as);
|
fmt::basic_format_args<fmt::wprintf_context> args(as);
|
||||||
|
@ -599,30 +588,7 @@ TEST(PrintfTest, VSPrintfMakeWArgsExample) {
|
||||||
fmt::basic_format_args<fmt::wprintf_context> args2(as2);
|
fmt::basic_format_args<fmt::wprintf_context> args2(as2);
|
||||||
EXPECT_EQ(L"[42] something happened",
|
EXPECT_EQ(L"[42] something happened",
|
||||||
fmt::vsprintf(L"[%d] %s happened", args2));
|
fmt::vsprintf(L"[%d] %s happened", args2));
|
||||||
// the older gcc versions can't cast the return value
|
|
||||||
#if !defined(__GNUC__) || (__GNUC__ > 4)
|
|
||||||
EXPECT_EQ(L"[42] something happened",
|
EXPECT_EQ(L"[42] something happened",
|
||||||
fmt::vsprintf(L"[%d] %s happened",
|
fmt::vsprintf(L"[%d] %s happened",
|
||||||
{fmt::make_wprintf_args(42, L"something")}));
|
{fmt::make_wprintf_args(42, L"something")}));
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(PrintfTest, PrintfDetermineOutputSize) {
|
|
||||||
using backit = std::back_insert_iterator<std::vector<char>>;
|
|
||||||
using truncated_printf_context =
|
|
||||||
fmt::basic_printf_context<fmt::detail::truncating_iterator<backit>, char>;
|
|
||||||
|
|
||||||
auto v = std::vector<char>{};
|
|
||||||
auto it = std::back_inserter(v);
|
|
||||||
|
|
||||||
const auto format_string = "%s";
|
|
||||||
const auto format_arg = "Hello";
|
|
||||||
const auto expected_size = fmt::sprintf(format_string, format_arg).size();
|
|
||||||
|
|
||||||
EXPECT_EQ((truncated_printf_context(
|
|
||||||
fmt::detail::truncating_iterator<backit>(it, 0), format_string,
|
|
||||||
fmt::make_format_args<truncated_printf_context>(format_arg))
|
|
||||||
.format()
|
|
||||||
.count()),
|
|
||||||
expected_size);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,106 +11,104 @@
|
||||||
|
|
||||||
#include "fmt/ranges.h"
|
#include "fmt/ranges.h"
|
||||||
|
|
||||||
#include "gtest.h"
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// Check if 'if constexpr' is supported.
|
#include "gtest/gtest.h"
|
||||||
#if (__cplusplus > 201402L) || \
|
|
||||||
(defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
|
|
||||||
|
|
||||||
# include <array>
|
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601
|
||||||
# include <map>
|
# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
# include <string>
|
#endif
|
||||||
# include <vector>
|
|
||||||
|
|
||||||
TEST(RangesTest, FormatVector) {
|
#if !FMT_MSC_VER || FMT_MSC_VER > 1910
|
||||||
std::vector<int32_t> iv{1, 2, 3, 5, 7, 11};
|
# define FMT_RANGES_TEST_ENABLE_JOIN
|
||||||
auto ivf = fmt::format("{}", iv);
|
# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
||||||
EXPECT_EQ("{1, 2, 3, 5, 7, 11}", ivf);
|
#endif
|
||||||
|
|
||||||
|
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
|
TEST(ranges_test, format_array) {
|
||||||
|
int arr[] = {1, 2, 3, 5, 7, 11};
|
||||||
|
EXPECT_EQ(fmt::format("{}", arr), "[1, 2, 3, 5, 7, 11]");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RangesTest, FormatVector2) {
|
TEST(ranges_test, format_2d_array) {
|
||||||
std::vector<std::vector<int32_t>> ivv{{1, 2}, {3, 5}, {7, 11}};
|
int arr[][2] = {{1, 2}, {3, 5}, {7, 11}};
|
||||||
auto ivf = fmt::format("{}", ivv);
|
EXPECT_EQ(fmt::format("{}", arr), "[[1, 2], [3, 5], [7, 11]]");
|
||||||
EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", ivf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RangesTest, FormatMap) {
|
TEST(ranges_test, format_array_of_literals) {
|
||||||
std::map<std::string, int32_t> simap{{"one", 1}, {"two", 2}};
|
const char* arr[] = {"1234", "abcd"};
|
||||||
EXPECT_EQ("{(\"one\", 1), (\"two\", 2)}", fmt::format("{}", simap));
|
EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
|
||||||
|
}
|
||||||
|
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||||
|
|
||||||
|
TEST(ranges_test, format_vector) {
|
||||||
|
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
||||||
|
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RangesTest, FormatPair) {
|
TEST(ranges_test, format_vector2) {
|
||||||
std::pair<int64_t, float> pa1{42, 1.5f};
|
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
|
||||||
EXPECT_EQ("(42, 1.5)", fmt::format("{}", pa1));
|
EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RangesTest, FormatTuple) {
|
TEST(ranges_test, format_map) {
|
||||||
std::tuple<int64_t, float, std::string, char> t{42, 1.5f, "this is tuple",
|
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
|
||||||
'i'};
|
EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]");
|
||||||
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t));
|
|
||||||
EXPECT_EQ("()", fmt::format("{}", std::tuple<>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RangesTest, JoinTuple) {
|
TEST(ranges_test, format_pair) {
|
||||||
// Value tuple args
|
auto p = std::pair<int, float>(42, 1.5f);
|
||||||
std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f);
|
EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
|
||||||
EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", ")));
|
|
||||||
|
|
||||||
// Testing lvalue tuple args
|
|
||||||
int x = 4;
|
|
||||||
std::tuple<char, int&> t2{'b', x};
|
|
||||||
EXPECT_EQ("b + 4", fmt::format("{}", fmt::join(t2, " + ")));
|
|
||||||
|
|
||||||
// Empty tuple
|
|
||||||
std::tuple<> t3;
|
|
||||||
EXPECT_EQ("", fmt::format("{}", fmt::join(t3, "|")));
|
|
||||||
|
|
||||||
// Single element tuple
|
|
||||||
std::tuple<float> t4{4.0f};
|
|
||||||
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RangesTest, JoinInitializerList) {
|
TEST(ranges_test, format_tuple) {
|
||||||
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join({1, 2, 3}, ", ")));
|
auto t =
|
||||||
EXPECT_EQ("fmt rocks !",
|
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
|
||||||
fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")));
|
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct my_struct {
|
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
||||||
int32_t i;
|
struct tuple_like {
|
||||||
std::string str; // can throw
|
int i;
|
||||||
template <size_t N> decltype(auto) get() const noexcept {
|
std::string str;
|
||||||
if constexpr (N == 0)
|
|
||||||
return i;
|
template <size_t N> fmt::enable_if_t<N == 0, int> get() const noexcept {
|
||||||
else if constexpr (N == 1)
|
return i;
|
||||||
return fmt::string_view{str};
|
}
|
||||||
|
template <size_t N>
|
||||||
|
fmt::enable_if_t<N == 1, fmt::string_view> get() const noexcept {
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t N> decltype(auto) get(const my_struct& s) noexcept {
|
template <size_t N>
|
||||||
return s.get<N>();
|
auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
|
||||||
|
return t.get<N>();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {};
|
||||||
|
|
||||||
template <> struct tuple_size<my_struct> : std::integral_constant<size_t, 2> {};
|
template <size_t N> struct tuple_element<N, tuple_like> {
|
||||||
|
using type = decltype(std::declval<tuple_like>().get<N>());
|
||||||
template <size_t N> struct tuple_element<N, my_struct> {
|
|
||||||
using type = decltype(std::declval<my_struct>().get<N>());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
TEST(RangesTest, FormatStruct) {
|
TEST(ranges_test, format_struct) {
|
||||||
my_struct mst{13, "my struct"};
|
auto t = tuple_like{42, "foo"};
|
||||||
EXPECT_EQ("(13, \"my struct\")", fmt::format("{}", mst));
|
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
|
||||||
}
|
}
|
||||||
|
#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
|
||||||
|
|
||||||
TEST(RangesTest, FormatTo) {
|
TEST(ranges_test, format_to) {
|
||||||
char buf[10];
|
char buf[10];
|
||||||
auto end = fmt::format_to(buf, "{}", std::vector{1, 2, 3});
|
auto end = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3});
|
||||||
*end = '\0';
|
*end = '\0';
|
||||||
EXPECT_STREQ(buf, "{1, 2, 3}");
|
EXPECT_STREQ(buf, "[1, 2, 3]");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct path_like {
|
struct path_like {
|
||||||
|
@ -120,13 +118,10 @@ struct path_like {
|
||||||
operator std::string() const;
|
operator std::string() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(RangesTest, PathLike) {
|
TEST(ranges_test, path_like) {
|
||||||
EXPECT_FALSE((fmt::is_range<path_like, char>::value));
|
EXPECT_FALSE((fmt::is_range<path_like, char>::value));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
|
|
||||||
// 201402L && _MSC_VER >= 1910)
|
|
||||||
|
|
||||||
#ifdef FMT_USE_STRING_VIEW
|
#ifdef FMT_USE_STRING_VIEW
|
||||||
struct string_like {
|
struct string_like {
|
||||||
const char* begin();
|
const char* begin();
|
||||||
|
@ -135,21 +130,135 @@ struct string_like {
|
||||||
explicit operator std::string_view() const { return "foo"; }
|
explicit operator std::string_view() const { return "foo"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(RangesTest, FormatStringLike) {
|
TEST(ranges_test, format_string_like) {
|
||||||
EXPECT_EQ("foo", fmt::format("{}", string_like()));
|
EXPECT_EQ(fmt::format("{}", string_like()), "foo");
|
||||||
}
|
}
|
||||||
#endif // FMT_USE_STRING_VIEW
|
#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
|
||||||
|
// information during iteration so they only provide non-const begin()/end().
|
||||||
|
template <typename T> class non_const_only_range {
|
||||||
|
private:
|
||||||
|
std::vector<T> vec;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using const_iterator = typename ::std::vector<T>::const_iterator;
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
explicit non_const_only_range(Args&&... args)
|
||||||
|
: vec(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
|
const_iterator begin() { return vec.begin(); }
|
||||||
|
const_iterator end() { return vec.end(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> class noncopyable_range {
|
||||||
|
private:
|
||||||
|
std::vector<T> vec;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using const_iterator = typename ::std::vector<T>::const_iterator;
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
explicit noncopyable_range(Args&&... args)
|
||||||
|
: vec(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
|
noncopyable_range(noncopyable_range const&) = delete;
|
||||||
|
noncopyable_range(noncopyable_range&) = delete;
|
||||||
|
|
||||||
|
const_iterator begin() const { return vec.begin(); }
|
||||||
|
const_iterator end() const { return vec.end(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ranges_test, range) {
|
||||||
|
noncopyable_range<int> w(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", w), "[0, 0, 0]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", noncopyable_range<int>(3u, 0)), "[0, 0, 0]");
|
||||||
|
|
||||||
|
non_const_only_range<int> x(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", x), "[0, 0, 0]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", non_const_only_range<int>(3u, 0)), "[0, 0, 0]");
|
||||||
|
|
||||||
|
auto y = std::vector<int>(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", y), "[0, 0, 0]");
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::vector<int>(3u, 0)), "[0, 0, 0]");
|
||||||
|
|
||||||
|
const auto z = std::vector<int>(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
|
||||||
|
struct unformattable {};
|
||||||
|
|
||||||
|
TEST(ranges_test, unformattable_range) {
|
||||||
|
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
|
||||||
|
fmt::format_context>::value));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FMT_RANGES_TEST_ENABLE_JOIN
|
||||||
|
TEST(ranges_test, join_tuple) {
|
||||||
|
// Value tuple args.
|
||||||
|
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
|
||||||
|
EXPECT_EQ(fmt::format("({})", fmt::join(t1, ", ")), "(a, 1, 2)");
|
||||||
|
|
||||||
|
// Testing lvalue tuple args.
|
||||||
|
int x = 4;
|
||||||
|
auto t2 = std::tuple<char, int&>('b', x);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(t2, " + ")), "b + 4");
|
||||||
|
|
||||||
|
// Empty tuple.
|
||||||
|
auto t3 = std::tuple<>();
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(t3, "|")), "");
|
||||||
|
|
||||||
|
// Single element tuple.
|
||||||
|
auto t4 = std::tuple<float>(4.0f);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ranges_test, join_initializer_list) {
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join({1, 2, 3}, ", ")), "1, 2, 3");
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")),
|
||||||
|
"fmt rocks !");
|
||||||
|
}
|
||||||
|
|
||||||
struct zstring_sentinel {};
|
struct zstring_sentinel {};
|
||||||
|
|
||||||
bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
|
bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
|
||||||
bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
|
bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
|
||||||
|
|
||||||
struct zstring {
|
struct zstring {
|
||||||
const char* p;
|
const char* p;
|
||||||
const char* begin() const { return p; }
|
const char* begin() const { return p; }
|
||||||
zstring_sentinel end() const { return {}; }
|
zstring_sentinel end() const { return {}; }
|
||||||
};
|
};
|
||||||
TEST(RangesTest, JoinSentinel) {
|
|
||||||
zstring hello{"hello"};
|
TEST(ranges_test, join_sentinel) {
|
||||||
EXPECT_EQ("{'h', 'e', 'l', 'l', 'o'}", fmt::format("{}", hello));
|
auto hello = zstring{"hello"};
|
||||||
EXPECT_EQ("h_e_l_l_o", fmt::format("{}", fmt::join(hello, "_")));
|
EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']");
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ranges_test, join_range) {
|
||||||
|
noncopyable_range<int> w(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(w, ",")), "0,0,0");
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(noncopyable_range<int>(3u, 0), ",")),
|
||||||
|
"0,0,0");
|
||||||
|
|
||||||
|
non_const_only_range<int> x(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(x, ",")), "0,0,0");
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ",")),
|
||||||
|
"0,0,0");
|
||||||
|
|
||||||
|
auto y = std::vector<int>(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(y, ",")), "0,0,0");
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ",")),
|
||||||
|
"0,0,0");
|
||||||
|
|
||||||
|
const auto z = std::vector<int>(3u, 0);
|
||||||
|
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
|
||||||
|
}
|
||||||
|
#endif // FMT_RANGES_TEST_ENABLE_JOIN
|
||||||
|
|
|
@ -11,25 +11,25 @@
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
||||||
#include "gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest-extra.h"
|
#include "gtest-extra.h"
|
||||||
|
|
||||||
TEST(ScanTest, ReadText) {
|
TEST(scan_test, read_text) {
|
||||||
fmt::string_view s = "foo";
|
auto s = fmt::string_view("foo");
|
||||||
auto end = fmt::scan(s, "foo");
|
auto end = fmt::scan(s, "foo");
|
||||||
EXPECT_EQ(end, s.end());
|
EXPECT_EQ(end, s.end());
|
||||||
EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input");
|
EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, ReadInt) {
|
TEST(scan_test, read_int) {
|
||||||
int n = 0;
|
auto n = int();
|
||||||
fmt::scan("42", "{}", n);
|
fmt::scan("42", "{}", n);
|
||||||
EXPECT_EQ(n, 42);
|
EXPECT_EQ(n, 42);
|
||||||
fmt::scan("-42", "{}", n);
|
fmt::scan("-42", "{}", n);
|
||||||
EXPECT_EQ(n, -42);
|
EXPECT_EQ(n, -42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, ReadLongLong) {
|
TEST(scan_test, read_longlong) {
|
||||||
long long n = 0;
|
long long n = 0;
|
||||||
fmt::scan("42", "{}", n);
|
fmt::scan("42", "{}", n);
|
||||||
EXPECT_EQ(n, 42);
|
EXPECT_EQ(n, 42);
|
||||||
|
@ -37,15 +37,15 @@ TEST(ScanTest, ReadLongLong) {
|
||||||
EXPECT_EQ(n, -42);
|
EXPECT_EQ(n, -42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, ReadUInt) {
|
TEST(scan_test, read_uint) {
|
||||||
unsigned n = 0;
|
auto n = unsigned();
|
||||||
fmt::scan("42", "{}", n);
|
fmt::scan("42", "{}", n);
|
||||||
EXPECT_EQ(n, 42);
|
EXPECT_EQ(n, 42);
|
||||||
EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error,
|
EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error,
|
||||||
"invalid input");
|
"invalid input");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, ReadULongLong) {
|
TEST(scan_test, read_ulonglong) {
|
||||||
unsigned long long n = 0;
|
unsigned long long n = 0;
|
||||||
fmt::scan("42", "{}", n);
|
fmt::scan("42", "{}", n);
|
||||||
EXPECT_EQ(n, 42);
|
EXPECT_EQ(n, 42);
|
||||||
|
@ -53,14 +53,14 @@ TEST(ScanTest, ReadULongLong) {
|
||||||
"invalid input");
|
"invalid input");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, ReadString) {
|
TEST(scan_test, read_string) {
|
||||||
std::string s;
|
auto s = std::string();
|
||||||
fmt::scan("foo", "{}", s);
|
fmt::scan("foo", "{}", s);
|
||||||
EXPECT_EQ(s, "foo");
|
EXPECT_EQ(s, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, ReadStringView) {
|
TEST(scan_test, read_string_view) {
|
||||||
fmt::string_view s;
|
auto s = fmt::string_view();
|
||||||
fmt::scan("foo", "{}", s);
|
fmt::scan("foo", "{}", s);
|
||||||
EXPECT_EQ(s, "foo");
|
EXPECT_EQ(s, "foo");
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,8 @@ template <> struct scanner<tm> {
|
||||||
};
|
};
|
||||||
} // namespace fmt
|
} // namespace fmt
|
||||||
|
|
||||||
TEST(ScanTest, ReadCustom) {
|
TEST(scan_test, read_custom) {
|
||||||
const char* input = "Date: 1985-10-25";
|
auto input = "Date: 1985-10-25";
|
||||||
auto t = tm();
|
auto t = tm();
|
||||||
fmt::scan(input, "Date: {0:%Y-%m-%d}", t);
|
fmt::scan(input, "Date: {0:%Y-%m-%d}", t);
|
||||||
EXPECT_EQ(t.tm_year, 85);
|
EXPECT_EQ(t.tm_year, 85);
|
||||||
|
@ -100,16 +100,16 @@ TEST(ScanTest, ReadCustom) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST(ScanTest, InvalidFormat) {
|
TEST(scan_test, invalid_format) {
|
||||||
EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error,
|
EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error,
|
||||||
"argument index out of range");
|
"argument index out of range");
|
||||||
EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error,
|
EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error,
|
||||||
"invalid format string");
|
"invalid format string");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ScanTest, Example) {
|
TEST(scan_test, example) {
|
||||||
std::string key;
|
auto key = std::string();
|
||||||
int value;
|
auto value = int();
|
||||||
fmt::scan("answer = 42", "{} = {}", key, value);
|
fmt::scan("answer = 42", "{} = {}", key, value);
|
||||||
EXPECT_EQ(key, "answer");
|
EXPECT_EQ(key, "answer");
|
||||||
EXPECT_EQ(value, 42);
|
EXPECT_EQ(value, 42);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue