diff --git a/.gitignore b/.gitignore index 600a6424..faa768a5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ CTestTestfile.cmake CMakeCache.txt CMakeFiles Makefile +run-msbuild.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index 96dd8416..1fb4a707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,11 @@ endif () option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) +# Options that control generation of various targets. +option(FMT_DOC "Generate the doc target." ON) +option(FMT_INSTALL "Generate the install target." ON) +option(FMT_TEST "Generate the test target." ON) + project(FORMAT) message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") @@ -90,7 +95,7 @@ if (BIICODE) endif () add_library(cppformat ${FMT_SOURCES}) -if (BUILD_SHARED_LIBS) +if (BUILD_SHARED_LIBS AND UNIX AND NOT APPLE) # Fix rpmlint warning: # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6. target_link_libraries(cppformat -Wl,--as-needed) @@ -106,89 +111,21 @@ endif () # over compile options, so the options used here only matter for testing. if (CPP11_FLAG AND FMT_PEDANTIC) set(FMT_EXTRA_COMPILE_FLAGS "${FMT_EXTRA_COMPILE_FLAGS} ${CPP11_FLAG}") - # Test compilation with default flags. - file(GLOB src RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} test/*.cc test/*.h) - add_library(testformat STATIC ${FMT_SOURCE_FILES} ${src}) + set(FMT_TEST_DEFAULT_FLAGS TRUE) endif () set_target_properties(cppformat PROPERTIES COMPILE_FLAGS "${FMT_EXTRA_COMPILE_FLAGS}") -add_subdirectory(doc) - -include_directories(. gmock) - -# 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/gmock-gtest-all.cc gmock/gmock/gmock.h - gmock/gtest/gtest.h gmock/gtest/gtest-spi.h) -find_package(Threads) -if (Threads_FOUND) - target_link_libraries(gmock ${CMAKE_THREAD_LIBS_INIT}) -else () - target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0) +if (FMT_DOC) + add_subdirectory(doc) endif () -# Check if variadic templates are working and not affected by GCC bug 39653: -# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39653 -check_cxx_source_compiles(" - template - struct S { typedef typename S::type type; }; - int main() {}" FMT_VARIADIC_TEMPLATES) - -# Check if initializer lists are supported. -check_cxx_source_compiles(" - #include - int main() {}" FMT_INITIALIZER_LIST) - -if (NOT FMT_VARIADIC_TEMPLATES OR NOT FMT_INITIALIZER_LIST) - add_definitions(-DGTEST_LANG_CXX11=0) +if (FMT_TEST) + enable_testing() + add_subdirectory(test) endif () -# This is disabled at the moment because format is compiled without -std=c++11 -# by default. -#check_cxx_source_compiles(" -# void f() noexcept {} -# int main(){ f(); }" FMT_BASIC_NOEXCEPT_SUPPORT) -#if (FMT_BASIC_NOEXCEPT_SUPPORT) -# add_definitions(-DFMT_USE_NOEXCEPT=1) -#endif () - -#check_cxx_source_compiles(" -# struct C{ -# C()=delete; -# C(const C&)=delete; -# C& operator=(const C&)=delete; -# }; -# int main(){}" FMT_DELETED_FUNCTIONS) -#if (FMT_DELETED_FUNCTIONS) -# add_definitions(-DFMT_USE_DELETED_FUNCTIONS=1) -#endif () - -#check_cxx_source_compiles(" -# static_assert(true, \"\"); -# int main(){}" FMT_STATIC_ASSERT) -#if (FMT_STATIC_ASSERT) -# add_definitions(-DFMT_USE_STATIC_ASSERT=1) -#endif () - -# Workaround a bug in implementation of variadic templates in MSVC11. -if (MSVC) - target_compile_definitions(gmock PUBLIC _VARIADIC_MAX=10) -endif () - -# GTest doesn't detect with clang. -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - target_compile_definitions(gmock PUBLIC GTEST_USE_OWN_TR1_TUPLE=1) -endif () - -enable_testing() -add_subdirectory(test) - set(CPACK_PACKAGE_VERSION_MAJOR 1) set(CPACK_PACKAGE_VERSION_MINOR 2) set(CPACK_PACKAGE_VERSION_PATCH 0) @@ -206,7 +143,8 @@ if (EXISTS .gitignore) string(REPLACE "*" ".*" line "${line}") set(ignored_files ${ignored_files} "${line}$" "${line}/") endforeach () - set(ignored_files ${ignored_files} /.git /breathe /format-benchmark sphinx/) + set(ignored_files ${ignored_files} + /.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees) set(CPACK_SOURCE_GENERATOR ZIP) set(CPACK_SOURCE_IGNORE_FILES ${ignored_files}) @@ -216,7 +154,9 @@ if (EXISTS .gitignore) endif () # Install targets. -set(FMT_LIB_DIR lib CACHE STRING - "Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.") -install(TARGETS cppformat DESTINATION ${FMT_LIB_DIR}) -install(FILES format.h DESTINATION include/cppformat) +if (FMT_INSTALL) + set(FMT_LIB_DIR lib CACHE STRING + "Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.") + install(TARGETS cppformat DESTINATION ${FMT_LIB_DIR}) + install(FILES format.h DESTINATION include/cppformat) +endif () diff --git a/README.rst b/README.rst index 29f43348..fb4e38c7 100644 --- a/README.rst +++ b/README.rst @@ -149,6 +149,8 @@ Projects using this library * `readpe `_: Read Portable Executable +* `redis-cerberus `_: A Redis cluster proxy + * `Saddy `_: Small crossplatform 2D graphic engine @@ -157,6 +159,10 @@ Projects using this library * `spdlog `_: Super fast C++ logging library +* `Stellar `_: Financial platform + +* `Touch Surgery `_: Surgery simulator + * `TrinityCore `_: Open-source MMORPG framework `More... `_ diff --git a/appveyor.yml b/appveyor.yml index dfebef22..9e7db412 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,14 +1,14 @@ +configuration: + - Debug + - Release + environment: CTEST_OUTPUT_ON_FAILURE: 1 matrix: - BUILD: msvc - CONFIG: Debug - BUILD: msvc - CONFIG: Release + PLATFORM: x64 - BUILD: mingw - CONFIG: Debug - - BUILD: mingw - CONFIG: Release build_script: - python support/appveyor-build.py diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 2990e8fc..e3cab6aa 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -6,4 +6,5 @@ endif () add_custom_target(doc COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py) -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ DESTINATION share/doc/cppformat) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html + DESTINATION share/doc/cppformat) diff --git a/doc/api.rst b/doc/api.rst index 7e1855a7..ecd442a1 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -26,6 +26,8 @@ arguments in the resulting string. .. doxygenfunction:: format(CStringRef, ArgList) +.. doxygenfunction:: operator""_format(const char *, std::size_t) + .. _print: .. doxygenfunction:: print(CStringRef, ArgList) @@ -74,6 +76,8 @@ Utilities .. doxygenfunction:: fmt::arg(StringRef, const T&) +.. doxygenfunction:: operator""_a(const char *, std::size_t) + .. doxygendefine:: FMT_CAPTURE .. doxygendefine:: FMT_VARIADIC diff --git a/doc/build.py b/doc/build.py index 340ce504..d4266c6b 100755 --- a/doc/build.py +++ b/doc/build.py @@ -1,26 +1,22 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python # Build the documentation. from __future__ import print_function -import os, shutil, tempfile -from subprocess import check_call, CalledProcessError, Popen, PIPE +import errno, os, shutil, sys, tempfile +from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE +from distutils.version import LooseVersion -def pip_install(package, commit=None): +def pip_install(package, commit=None, **kwargs): "Install package using pip." if commit: - cmd = ['pip', 'show', package.split('/')[1]] - p = Popen(cmd, stdout=PIPE, stderr=PIPE) - stdout, stderr = p.communicate() - if stdout: - return # Already installed - elif p.returncode != 0: - # Old versions of pip such as the one installed on Travis don't support - # the show command - continue installation in this case. - # Otherwise throw CalledProcessError. - if p.returncode > 1 and 'No command by the name pip show' not in stderr: - raise CalledProcessError(p.returncode, cmd) + check_version = kwargs.get('check_version', '') + #output = check_output(['pip', 'show', package.split('/')[1]]) + #if check_version in output: + # print('{} already installed'.format(package)) + # return package = 'git+git://github.com/{0}.git@{1}'.format(package, commit) - check_call(['pip', 'install', '-q', package]) + print('Installing {}'.format(package)) + check_call(['pip', 'install', '--upgrade', package]) def build_docs(): # Create virtualenv. @@ -28,9 +24,28 @@ def build_docs(): virtualenv_dir = 'virtualenv' check_call(['virtualenv', virtualenv_dir]) activate_this_file = os.path.join(virtualenv_dir, 'bin', 'activate_this.py') - execfile(activate_this_file, dict(__file__=activate_this_file)) + with open(activate_this_file) as f: + exec(f.read(), dict(__file__=activate_this_file)) + # Upgrade pip because installation of sphinx with pip 1.1 available on Travis + # is broken (see #207) and it doesn't support the show command. + from pkg_resources import get_distribution, DistributionNotFound + pip_version = get_distribution('pip').version + if LooseVersion(pip_version) < LooseVersion('1.5.4'): + print("Updating pip") + 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==1.3.1') + pip_install('cppformat/sphinx', + '12dde8afdb0a7bb5576e2656692c3478c69d8cc3', + check_version='1.4a0.dev-20151013') pip_install('michaeljones/breathe', '511b0887293e7c6b12310bb61b3659068f48f0f4') # Build docs. @@ -43,7 +58,6 @@ def build_docs(): GENERATE_RTF = NO CASE_SENSE_NAMES = NO INPUT = {0}/format.h - EXCLUDE_SYMBOLS = fmt::internal::* QUIET = YES JAVADOC_AUTOBRIEF = YES AUTOLINK_SUPPORT = NO @@ -54,18 +68,25 @@ def build_docs(): ALIASES += "endrst=\endverbatim" PREDEFINED = _WIN32=1 \ FMT_USE_VARIADIC_TEMPLATES=1 \ - FMT_USE_RVALUE_REFERENCES=1 + FMT_USE_RVALUE_REFERENCES=1 \ + FMT_USE_USER_DEFINED_LITERALS=1 EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str - '''.format(os.path.dirname(doc_dir))) + '''.format(os.path.dirname(doc_dir)).encode('UTF-8')) if p.returncode != 0: raise CalledProcessError(p.returncode, cmd) check_call(['sphinx-build', '-D', 'breathe_projects.format=' + os.path.join(os.getcwd(), 'doxyxml'), '-b', 'html', doc_dir, 'html']) - check_call(['lessc', '--clean-css', - '--include-path=' + os.path.join(doc_dir, 'bootstrap'), - os.path.join(doc_dir, 'cppformat.less'), - 'html/_static/cppformat.css']) + try: + check_call(['lessc', '--clean-css', + '--include-path=' + os.path.join(doc_dir, 'bootstrap'), + os.path.join(doc_dir, 'cppformat.less'), + 'html/_static/cppformat.css']) + except OSError as e: + if e.errno != errno.ENOENT: + raise + print('lessc not found; make sure that Less (http://lesscss.org/) is installed') + sys.exit(1) return 'html' if __name__ == '__main__': diff --git a/doc/cppformat.less b/doc/cppformat.less index 44772375..f3e78a65 100644 --- a/doc/cppformat.less +++ b/doc/cppformat.less @@ -13,11 +13,6 @@ html { background-color: darken(@header-bg, 10%); } -.navbar-content { - .make-md-column-offset(2); - .make-md-column(8); -} - .jumbotron { #gradient > .vertical(@header-bg; darken(@header-bg, 2%); 50%; 50%); background-size: 100% 4px; @@ -32,9 +27,11 @@ div.sphinxsidebar { } // Keep content not too wide for better readability. -.content { - .make-md-column-offset(2); - .make-md-column(8); +.navbar-content, .content { + .make-md-column-offset(1); + .make-md-column(10); + .make-lg-column-offset(2); + .make-lg-column(8); } .footer { diff --git a/doc/index.rst b/doc/index.rst index 2166b253..35c56f04 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -65,6 +65,34 @@ The Format API also supports positional arguments useful for localization: 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 +what goes where when multiple values are being inserted: + +.. code:: c++ + + fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.", + fmt::arg("name", "World"), fmt::arg("number", 42)); + +If your compiler supports C++11 user-defined literals, the suffix ``_a`` offers +an alternative, slightly terser syntax for named arguments: + +.. code:: c++ + + fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.", + "name"_a="World", "number"_a=42); + +The ``_format`` suffix may be used to format string literals similar to Python: + +.. code:: c++ + + std::string message = "{0}{1}{0}"_format("abra", "cad"); + +Other than the placement of the format string on the left of the operator, +``_format`` is functionally identical to ``fmt::format``. In order to use the +literal operators, they must be made visible with the directive +``using namespace fmt::literals;``. Note that this brings in only ``_a`` and +``_format`` but nothing else from the ``fmt`` namespace. + .. _write-api: Write API @@ -128,13 +156,22 @@ compilers where it has been tested and known to work: * Mac OS X with GCC 4.2.1 and Clang 4.2, 5.1.0 -* 64-bit Windows with Visual C++ 2010 and - `2013 `_ +* 64-bit Windows with Visual C++ 2010, 2013 and + `2015 `_ * 32-bit Windows with Visual C++ 2010 Although the library uses C++11 features when available, it also works with older -compilers and standard library implementations. +compilers and standard library implementations. The only thing to keep in mind +for C++98 portability: + +* Variadic templates: minimum GCC 4.4, Clang 2.9 or VS2013. This feature allows + the Format API to accept an unlimited number of arguments. With older compilers + the maximum is 15. + +* User-defined literals: minimum GCC 4.7, Clang 3.1 or VS2015. The suffixes + ``_format`` and ``_a`` are functionally equivalent to the functions + ``fmt::format`` and ``fmt::arg``. The output of all formatting functions is consistent across platforms. In particular, formatting a floating-point infinity always gives ``inf`` while the output diff --git a/doc/usage.rst b/doc/usage.rst index ea3e43be..bee62ed2 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -54,6 +54,23 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to __ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries +Building the documentation +========================== + +To build the documentation you need the following software installed on your +system: + +* `Python `_ with pip and virtualenv +* `Doxygen `_ +* `Less `_ with less-plugin-clean-css + +First generate makefiles or project files using CMake as described in +the previous section. Then compile the ``doc`` target/project, for example:: + + make doc + +This will generate the HTML documenation in ``doc/html``. + Android NDK =========== diff --git a/format.cc b/format.cc index 666f69c2..a9c978b9 100644 --- a/format.cc +++ b/format.cc @@ -523,6 +523,13 @@ class PrintfArgFormatter : } *out = static_cast(value); } + + void visit_custom(Arg::CustomValue c) { + BasicFormatter formatter(ArgList(), this->writer()); + const Char format_str[] = {'}', 0}; + const Char *format = format_str; + c.format(&formatter, c.value, &format); + } }; } // namespace internal } // namespace fmt @@ -612,14 +619,17 @@ FMT_FUNC void fmt::internal::report_unknown_type(char code, const char *type) { #if FMT_USE_WINDOWS_H FMT_FUNC fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) { - int length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s.size(), 0, 0); static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast(s.size()); + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, 0, 0); if (length == 0) FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); buffer_.resize(length + 1); length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s.size(), &buffer_[0], length); + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); if (length == 0) FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); buffer_[length] = 0; @@ -633,12 +643,15 @@ FMT_FUNC fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) { } FMT_FUNC int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) { - int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s.size(), 0, 0, 0, 0); + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast(s.size()); + int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, 0, 0, 0, 0); if (length == 0) return GetLastError(); buffer_.resize(length + 1); length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s.size(), &buffer_[0], length, 0, 0); + CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, 0, 0); if (length == 0) return GetLastError(); buffer_[length] = 0; diff --git a/format.h b/format.h index d384cfef..2f714001 100644 --- a/format.h +++ b/format.h @@ -38,9 +38,16 @@ #include #include #include -#include #include +#ifndef FMT_USE_IOSTREAMS +# define FMT_USE_IOSTREAMS 1 +#endif + +#if FMT_USE_IOSTREAMS +# include +#endif + #if _SECURE_SCL # include #endif @@ -177,6 +184,16 @@ inline uint32_t clzll(uint64_t x) { TypeName& operator=(const TypeName&) #endif +#ifndef FMT_USE_USER_DEFINED_LITERALS +// All compilers which support UDLs also support variadic templates. This +// makes the fmt::literals implementation easier. However, an explicit check +// for variadic templates is added here just in case. +# define FMT_USE_USER_DEFINED_LITERALS \ + FMT_USE_VARIADIC_TEMPLATES && \ + (FMT_HAS_FEATURE(cxx_user_literals) || \ + (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1900) +#endif + #ifndef FMT_ASSERT # define FMT_ASSERT(condition, message) assert((condition) && message) #endif @@ -285,7 +302,6 @@ class BasicStringRef { typedef BasicStringRef StringRef; typedef BasicStringRef WStringRef; - /** \rst A reference to a null terminated string. It can be constructed from a C @@ -449,9 +465,9 @@ class MemoryBuffer : private Allocator, public Buffer { private: T data_[SIZE]; - // Free memory allocated by the buffer. - void free() { - if (this->ptr_ != data_) this->deallocate(this->ptr_, this->capacity_); + // Deallocate memory allocated by the buffer. + void deallocate() { + if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_); } protected: @@ -460,7 +476,7 @@ class MemoryBuffer : private Allocator, public Buffer { public: explicit MemoryBuffer(const Allocator &alloc = Allocator()) : Allocator(alloc), Buffer(data_, SIZE) {} - ~MemoryBuffer() { free(); } + ~MemoryBuffer() { deallocate(); } #if FMT_USE_RVALUE_REFERENCES private: @@ -477,7 +493,7 @@ class MemoryBuffer : private Allocator, public Buffer { } else { this->ptr_ = other.ptr_; // Set pointer to the inline array so that delete is not called - // when freeing. + // when deallocating. other.ptr_ = other.data_; } } @@ -489,7 +505,7 @@ class MemoryBuffer : private Allocator, public Buffer { MemoryBuffer &operator=(MemoryBuffer &&other) { assert(this != &other); - free(); + deallocate(); move(other); return *this; } @@ -515,7 +531,7 @@ void MemoryBuffer::grow(std::size_t size) { // the buffer already uses the new storage and will deallocate it in case // of exception. if (old_ptr != data_) - this->deallocate(old_ptr, old_capacity); + Allocator::deallocate(old_ptr, old_capacity); } // A fixed-size buffer. @@ -712,24 +728,23 @@ inline unsigned count_digits(uint32_t n) { // Formats a decimal unsigned integer value writing into buffer. template inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { - --num_digits; + buffer += num_digits; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = (value % 100) * 2; value /= 100; - buffer[num_digits] = Data::DIGITS[index + 1]; - buffer[num_digits - 1] = Data::DIGITS[index]; - num_digits -= 2; + *--buffer = Data::DIGITS[index + 1]; + *--buffer = Data::DIGITS[index]; } if (value < 10) { - *buffer = static_cast('0' + value); + *--buffer = static_cast('0' + value); return; } unsigned index = static_cast(value * 2); - buffer[1] = Data::DIGITS[index + 1]; - buffer[0] = Data::DIGITS[index]; + *--buffer = Data::DIGITS[index + 1]; + *--buffer = Data::DIGITS[index]; } #ifndef _WIN32 @@ -1047,7 +1062,7 @@ struct NamedArg : Arg { template NamedArg(BasicStringRef argname, const T &value) - : name(argname), Arg(MakeValue(value)) { + : Arg(MakeValue(value)), name(argname) { type = static_cast(MakeValue::type(value)); } }; @@ -2677,17 +2692,6 @@ void print(std::FILE *f, CStringRef format_str, ArgList args); */ void print(CStringRef format_str, ArgList args); -/** - \rst - Prints formatted data to the stream *os*. - - **Example**:: - - print(cerr, "Don't {}!", "panic"); - \endrst - */ -void print(std::ostream &os, CStringRef format_str, ArgList args); - template void printf(BasicWriter &w, BasicCStringRef format, ArgList args) { internal::PrintfFormatter(args).format(w, format); @@ -2708,6 +2712,12 @@ inline std::string sprintf(CStringRef format, ArgList args) { return w.str(); } +inline std::wstring sprintf(WCStringRef format, ArgList args) { + WMemoryWriter w; + printf(w, format, args); + return w.str(); +} + /** \rst Prints formatted data to the file *f*. @@ -2993,12 +3003,90 @@ FMT_VARIADIC(std::string, format, CStringRef) FMT_VARIADIC_W(std::wstring, format, WCStringRef) FMT_VARIADIC(void, print, CStringRef) FMT_VARIADIC(void, print, std::FILE *, CStringRef) -FMT_VARIADIC(void, print, std::ostream &, CStringRef) + FMT_VARIADIC(void, print_colored, Color, CStringRef) FMT_VARIADIC(std::string, sprintf, CStringRef) +FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) FMT_VARIADIC(int, printf, CStringRef) FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) -} + +#if FMT_USE_IOSTREAMS +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + print(cerr, "Don't {}!", "panic"); + \endrst + */ +void print(std::ostream &os, CStringRef format_str, ArgList args); +FMT_VARIADIC(void, print, std::ostream &, CStringRef) +#endif +} // namespace fmt + +#if FMT_USE_USER_DEFINED_LITERALS +namespace fmt { +namespace internal { + +template +struct UdlFormat { + const Char *str; + + template + auto operator()(Args && ... args) const + -> decltype(format(str, std::forward(args)...)) { + return format(str, std::forward(args)...); + } +}; + +template +struct UdlArg { + const Char *str; + + template + NamedArg operator=(T &&value) const { + return {str, std::forward(value)}; + } +}; + +} // namespace internal + +inline namespace literals { + +/** + \rst + C++11 literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::UdlFormat +operator"" _format(const char *s, std::size_t) { return {s}; } +inline internal::UdlFormat +operator"" _format(const wchar_t *s, std::size_t) { return {s}; } + +/** + \rst + C++11 literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::UdlArg +operator"" _a(const char *s, std::size_t) { return {s}; } +inline internal::UdlArg +operator"" _a(const wchar_t *s, std::size_t) { return {s}; } + +} // inline namespace literals +} // namespace fmt +#endif // FMT_USE_USER_DEFINED_LITERALS // Restore warnings. #if FMT_GCC_VERSION >= 406 diff --git a/support/appveyor-build.py b/support/appveyor-build.py index 9f4e30ce..4967ae3b 100755 --- a/support/appveyor-build.py +++ b/support/appveyor-build.py @@ -5,7 +5,8 @@ import os from subprocess import check_call build = os.environ['BUILD'] -config = os.environ['CONFIG'] +config = os.environ['CONFIGURATION'] +platform = os.environ.get('PLATFORM') path = os.environ['PATH'] cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config] if build == 'mingw': @@ -19,6 +20,10 @@ else: # Add MSBuild 14.0 to PATH as described in # http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc. os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\14.0\Bin;' + path + generator = 'Visual Studio 14 2015' + if platform == 'x64': + generator += ' Win64' + cmake_command.append('-G' + generator) build_command = ['msbuild', '/m:4', '/p:Config=' + config, 'FORMAT.sln'] test_command = ['msbuild', 'RUN_TESTS.vcxproj'] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b23d4cc4..d30c222b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,48 @@ +set(FMT_GMOCK_DIR ../gmock) + +include_directories(.. ${FMT_GMOCK_DIR}) + +# 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 + ${FMT_GMOCK_DIR}/gmock-gtest-all.cc ${FMT_GMOCK_DIR}/gmock/gmock.h + ${FMT_GMOCK_DIR}/gtest/gtest.h ${FMT_GMOCK_DIR}/gtest/gtest-spi.h) +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 () + +# Check if variadic templates are working and not affected by GCC bug 39653: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39653 +check_cxx_source_compiles(" + template + struct S { typedef typename S::type type; }; + int main() {}" FMT_VARIADIC_TEMPLATES) + +# Check if initializer lists are supported. +check_cxx_source_compiles(" + #include + int main() {}" FMT_INITIALIZER_LIST) + +if (NOT FMT_VARIADIC_TEMPLATES OR NOT FMT_INITIALIZER_LIST) + add_definitions(-DGTEST_LANG_CXX11=0) +endif () + +# Workaround a bug in implementation of variadic templates in MSVC11. +if (MSVC) + target_compile_definitions(gmock PUBLIC _VARIADIC_MAX=10) +endif () + +# GTest doesn't detect with clang. +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_definitions(gmock PUBLIC GTEST_USE_OWN_TR1_TUPLE=1) +endif () + set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) add_library(test-main STATIC ${TEST_MAIN_SRC}) target_link_libraries(test-main cppformat gmock) @@ -14,14 +59,6 @@ function(add_fmt_test name) add_test(NAME ${name} COMMAND ${name}) endfunction() -check_cxx_source_compiles(" - enum C : char {A}; - int main() {}" - HAVE_ENUM_BASE) -if (HAVE_ENUM_BASE) - add_definitions(-DFMT_USE_ENUM_BASE=1) -endif () - add_fmt_test(assert-test) add_fmt_test(gtest-extra-test) add_fmt_test(format-test) @@ -45,6 +82,15 @@ if (CPP11_FLAG) set_target_properties(util-test PROPERTIES COMPILE_FLAGS ${CPP11_FLAG}) endif () +check_cxx_source_compiles(" + enum C : char {A}; + int main() {}" + HAVE_ENUM_BASE) +if (HAVE_ENUM_BASE) + set_target_properties(util-test + PROPERTIES COMPILE_DEFINITIONS "FMT_USE_ENUM_BASE=1") +endif () + foreach (src ${FMT_SOURCES}) set(FMT_TEST_SOURCES ${FMT_TEST_SOURCES} ../${src}) endforeach () @@ -55,7 +101,10 @@ check_cxx_source_compiles(" int main() { static_assert(!std::is_copy_assignable::value, \"\"); }" HAVE_TYPE_TRAITS) if (HAVE_TYPE_TRAITS) - add_definitions(-DFMT_USE_TYPE_TRAITS=1) + foreach (target format-test util-test) + set_target_properties(${target} + PROPERTIES COMPILE_DEFINITIONS "FMT_USE_TYPE_TRAITS=1") + endforeach () endif () add_executable(macro-test macro-test.cc ${FMT_TEST_SOURCES} ${TEST_MAIN_SRC}) @@ -82,6 +131,15 @@ if (HAVE_FNO_EXCEPTIONS_FLAG) PROPERTIES COMPILE_FLAGS -fno-exceptions) endif () +# Test compilation with default flags. +if (FMT_TEST_DEFAULT_FLAGS) + file(GLOB src RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cc *.h) + foreach (s ${FMT_SOURCES}) + set(src ${src} ../${s}) + endforeach () + add_library(testformat STATIC ${src}) +endif () + if (FMT_PEDANTIC) add_test(compile-test ${CMAKE_CTEST_COMMAND} --build-and-test diff --git a/test/format-test.cc b/test/format-test.cc index 35d96b45..3fceab88 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -148,19 +148,6 @@ TEST(CStringRefTest, Ctor) { EXPECT_STREQ("defg", CStringRef(std::string("defg")).c_str()); } -class TestString { - private: - std::string value_; - - public: - explicit TestString(const char *value = "") : value_(value) {} - - friend std::ostream &operator<<(std::ostream &os, const TestString &s) { - os << s.value_; - return os; - } -}; - #if FMT_USE_TYPE_TRAITS TEST(WriterTest, NotCopyConstructible) { EXPECT_FALSE(std::is_copy_constructible >::value); @@ -1615,3 +1602,33 @@ TEST(FormatTest, MaxArgs) { fmt::format("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e')); } + +#if FMT_USE_USER_DEFINED_LITERALS +// Passing user-defined literals directly to EXPECT_EQ causes problems +// with macro argument stringification (#) on some versions of GCC. +// Workaround: Assing the UDL result to a variable before the macro. + +using namespace fmt::literals; + +TEST(LiteralsTest, Format) { + auto udl_format = "{}c{}"_format("ab", 1); + EXPECT_EQ(format("{}c{}", "ab", 1), udl_format); + auto udl_format_w = L"{}c{}"_format(L"ab", 1); + EXPECT_EQ(format(L"{}c{}", L"ab", 1), udl_format_w); +} + +TEST(LiteralsTest, NamedArg) { + auto udl_a = format("{first}{second}{first}{third}", + "first"_a="abra", "second"_a="cad", "third"_a=99); + EXPECT_EQ(format("{first}{second}{first}{third}", + fmt::arg("first", "abra"), fmt::arg("second", "cad"), + fmt::arg("third", 99)), + udl_a); + auto udl_a_w = format(L"{first}{second}{first}{third}", + L"first"_a=L"abra", L"second"_a=L"cad", L"third"_a=99); + EXPECT_EQ(format(L"{first}{second}{first}{third}", + fmt::arg(L"first", L"abra"), fmt::arg(L"second", L"cad"), + fmt::arg(L"third", 99)), + udl_a_w); +} +#endif // FMT_USE_USER_DEFINED_LITERALS diff --git a/test/macro-test.cc b/test/macro-test.cc index 8a537028..38e85858 100644 --- a/test/macro-test.cc +++ b/test/macro-test.cc @@ -100,7 +100,7 @@ struct S {}; int test_variadic(FMT_GEN(10, GET_TYPE), const fmt::ArgList &args) { \ int result = 0; \ - for (std::size_t i = 0; args[i].type; ++i) \ + for (unsigned i = 0; args[i].type; ++i) \ result += args[i].int_value; \ return result; } diff --git a/test/printf-test.cc b/test/printf-test.cc index ec29bbe1..041fbad1 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -426,6 +426,10 @@ TEST(PrintfTest, Pointer) { EXPECT_PRINTF(fmt::format("{}", p), "%p", p); } +TEST(PrintfTest, Custom) { + EXPECT_PRINTF("abc", "%s", TestString("abc")); +} + TEST(PrintfTest, Location) { // TODO: test %n } @@ -452,3 +456,7 @@ TEST(PrintfTest, PrintfError) { EXPECT_LT(result, 0); } #endif + +TEST(PrintfTest, WideString) { + EXPECT_EQ(L"abc", fmt::sprintf(L"%s", TestWString(L"abc"))); +} diff --git a/test/test-main.cc b/test/test-main.cc index 9f5426ee..350044da 100644 --- a/test/test-main.cc +++ b/test/test-main.cc @@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #ifdef _WIN32 @@ -52,5 +53,10 @@ int main(int argc, char **argv) { _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + try { + return RUN_ALL_TESTS(); + } catch (...) { + // Catch all exceptions to make Coverity happy. + } + return EXIT_FAILURE; } diff --git a/test/util-test.cc b/test/util-test.cc index 2173b1bf..b92171ab 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -726,12 +726,14 @@ TEST(UtilTest, UTF8ToUTF16) { } template -void check_utf_conversion_error(const char *message) { +void check_utf_conversion_error( + const char *message, + fmt::BasicStringRef str = fmt::BasicStringRef(0, 0)) { fmt::MemoryWriter out; fmt::internal::format_windows_error(out, ERROR_INVALID_PARAMETER, message); fmt::SystemError error(0, ""); try { - Converter(fmt::BasicStringRef(0, 0)); + (Converter)(str); } catch (const fmt::SystemError &e) { error = e; } @@ -745,13 +747,17 @@ TEST(UtilTest, UTF16ToUTF8Error) { } TEST(UtilTest, UTF8ToUTF16Error) { + const char *message = "cannot convert string from UTF-8 to UTF-16"; + check_utf_conversion_error(message); check_utf_conversion_error( - "cannot convert string from UTF-8 to UTF-16"); + message, fmt::StringRef("foo", INT_MAX + 1u)); } TEST(UtilTest, UTF16ToUTF8Convert) { fmt::internal::UTF16ToUTF8 u; EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::WStringRef(0, 0))); + EXPECT_EQ(ERROR_INVALID_PARAMETER, + u.convert(fmt::WStringRef(L"foo", INT_MAX + 1u))); } #endif // _WIN32 diff --git a/test/util.h b/test/util.h index a0d485eb..7f42cecd 100644 --- a/test/util.h +++ b/test/util.h @@ -67,3 +67,26 @@ inline FILE *safe_fopen(const char *filename, const char *mode) { return std::fopen(filename, mode); #endif } + +template +class BasicTestString { + private: + std::basic_string value_; + + static const Char EMPTY[]; + + public: + explicit BasicTestString(const Char *value = EMPTY) : value_(value) {} + + friend std::basic_ostream &operator<<( + std::basic_ostream &os, const BasicTestString &s) { + os << s.value_; + return os; + } +}; + +template +const Char BasicTestString::EMPTY[] = {0}; + +typedef BasicTestString TestString; +typedef BasicTestString TestWString;