diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 522f9e1d3bf78..15582407dd517 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -60,6 +60,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11 with: sarif_file: results.sarif diff --git a/CMakeLists.txt b/CMakeLists.txt index 523c299bed904..e3b35110f8dc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,10 +355,14 @@ endif () add_library(fmt-header-only INTERFACE) add_library(fmt::fmt-header-only ALIAS fmt-header-only) -if (MSVC AND FMT_UNICODE) +if (NOT MSVC) + # Unicode is always supported on compilers other than MSVC. +elseif (FMT_UNICODE) # Unicode support requires compiling with /utf-8. target_compile_options(fmt PUBLIC $<$:/utf-8>) target_compile_options(fmt-header-only INTERFACE $<$:/utf-8>) +else () + target_compile_definitions(fmt PUBLIC FMT_UNICODE=0) endif () target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) @@ -464,12 +468,13 @@ function(add_doc_target) ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/support/python ${MKDOCS} build -f ${CMAKE_CURRENT_SOURCE_DIR}/support/mkdocs.yml - --site-dir ${CMAKE_CURRENT_BINARY_DIR}/site + # MkDocs requires the site dir to be outside of the doc dir. + --site-dir ${CMAKE_CURRENT_BINARY_DIR}/doc-html --no-directory-urls SOURCES ${sources}) include(GNUInstallDirs) - install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/support/site/ + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL) endfunction() @@ -504,8 +509,7 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) string(REPLACE "*" ".*" line "${line}") set(ignored_files ${ignored_files} "${line}$" "${line}/") endforeach () - set(ignored_files ${ignored_files} - /.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees) + set(ignored_files ${ignored_files} /.git /build/doxyxml .vagrant) set(CPACK_SOURCE_GENERATOR ZIP) set(CPACK_SOURCE_IGNORE_FILES ${ignored_files}) diff --git a/ChangeLog.md b/ChangeLog.md index b6ef8ac773269..8f165b73f3078 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,25 +1,49 @@ -# 11.0.0 - TBD +# 11.0.1 - 2024-07-05 + +- Fixed version number in the inline namespace + (https://github.com/fmtlib/fmt/issues/4047). + +- Fixed disabling Unicode support via CMake + (https://github.com/fmtlib/fmt/issues/4051). + +- Fixed deprecated `visit_format_arg` (https://github.com/fmtlib/fmt/pull/4043). + Thanks @nebkat. + +- Fixed handling of a sign and improved the `std::complex` formater + (https://github.com/fmtlib/fmt/pull/4034, + https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus. + +- Removed a redundant check in the formatter for `std::expected` + (https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus. + +# 11.0.0 - 2024-07-01 - Added `fmt/base.h` which provides a subset of the API with minimal include - dependencies and enough functionality to replace all uses of `*printf`. - This brings the compile time of code using {fmt} much closer to the - equivalent `printf` code as shown on the following benchmark that compiles - 100 source files: + dependencies and enough functionality to replace all uses of the `printf` + family of functions. This brings the compile time of code using {fmt} much + closer to the equivalent `printf` code as shown on the following benchmark + that compiles 100 source files: | Method | Compile Time (s) | |--------------|------------------| | printf | 1.6 | | IOStreams | 25.9 | - | fmt | 4.8 | + | fmt 10.x | 19.0 | + | fmt 11.0 | 4.8 | | tinyformat | 29.1 | | Boost Format | 55.0 | - Note that this is purely formatting code and includes. In real projects the - difference will be smaller partly because common standard headers will be - included in almost any translation unit anyway. + This gives almost 4x improvement in build speed compared to version 10. + Note that the benchmark is purely formatting code and includes. In real + projects the difference from `printf` will be smaller partly because common + standard headers will be included in almost any translation unit (TU) anyway. + In particular, in every case except `printf` above ~1s is spent in total on + including `` in all TUs. - Optimized includes in other headers such as `fmt/format.h` which is now - roughly equivalent to the old `fmt/core.h`. + roughly equivalent to the old `fmt/core.h` in terms of build speed. + +- Migrated the documentation at https://fmt.dev/ from Sphinx to MkDocs. - Improved C++20 module support (https://github.com/fmtlib/fmt/issues/3990, @@ -31,8 +55,10 @@ https://github.com/fmtlib/fmt/pull/4004, https://github.com/fmtlib/fmt/pull/4005, https://github.com/fmtlib/fmt/pull/4006, - https://github.com/fmtlib/fmt/pull/4013). In particular, native CMake support - for modules is now used if available. Thanks @yujincheng08. + https://github.com/fmtlib/fmt/pull/4013, + https://github.com/fmtlib/fmt/pull/4027, + https://github.com/fmtlib/fmt/pull/4029). In particular, native CMake support + for modules is now used if available. Thanks @yujincheng08 and @matt77hias. - Added an option to replace standard includes with `import std` enabled via the `FMT_IMPORT_STD` macro (https://github.com/fmtlib/fmt/issues/3921, @@ -60,11 +86,11 @@ Benchmark Time CPU Iterations ------------------------------------------------------- printf 81.8 ns 81.5 ns 8496899 - fmt::print (10.*) 63.8 ns 61.9 ns 11524151 + fmt::print (10.x) 63.8 ns 61.9 ns 11524151 fmt::print (11.0) 51.3 ns 51.0 ns 13846580 ``` -- Improved safety of `fmt::format_to` when writting to an array +- Improved safety of `fmt::format_to` when writing to an array (https://github.com/fmtlib/fmt/pull/3805). For example ([godbolt](https://www.godbolt.org/z/cYrn8dWY8)): @@ -73,7 +99,7 @@ auto result = fmt::format_to(volkswagen, "elephant"); ``` - no longer results in a buffer oveflow. Instead the output will be truncated + no longer results in a buffer overflow. Instead the output will be truncated and you can get the end iterator and whether truncation occurred from the `result` object. Thanks @ThePhD. @@ -86,7 +112,7 @@ compiled with Unicode enabled. - Added a formatter for `std::expected` - (https://github.com/fmtlib/fmt/pull/3834. Thanks @dominicpoeschko. + (https://github.com/fmtlib/fmt/pull/3834). Thanks @dominicpoeschko. - Added a formatter for `std::complex` (https://github.com/fmtlib/fmt/issues/1467, @@ -164,7 +190,7 @@ - Moved range and iterator overloads of `fmt::join` to `fmt/ranges.h`, next to other overloads. -- Fixed hanling of types with `begin` returning `void` such as Eigen matrices +- Fixed handling of types with `begin` returning `void` such as Eigen matrices (https://github.com/fmtlib/fmt/issues/3839, https://github.com/fmtlib/fmt/pull/3964). Thanks @Arghnews. @@ -215,7 +241,7 @@ - Improved named argument validation (https://github.com/fmtlib/fmt/issues/3817). -- Disabled copy contruction/assignment for `fmt::format_arg_store` and +- Disabled copy construction/assignment for `fmt::format_arg_store` and fixed moved construction (https://github.com/fmtlib/fmt/pull/3833). Thanks @ivafanas. @@ -255,7 +281,8 @@ https://github.com/fmtlib/fmt/pull/3980, https://github.com/fmtlib/fmt/pull/3988, https://github.com/fmtlib/fmt/pull/4010, - https://github.com/fmtlib/fmt/pull/4012). + https://github.com/fmtlib/fmt/pull/4012, + https://github.com/fmtlib/fmt/pull/4038). Thanks @vgorrX, @waywardmonkeys, @tchaikov and @phprus. - Fixed buffer overflow when using format string compilation with debug format @@ -310,7 +337,9 @@ https://github.com/fmtlib/fmt/pull/3968, https://github.com/fmtlib/fmt/pull/3972, https://github.com/fmtlib/fmt/pull/3983, + https://github.com/fmtlib/fmt/issues/3992, https://github.com/fmtlib/fmt/pull/3995, + https://github.com/fmtlib/fmt/pull/4009, https://github.com/fmtlib/fmt/pull/4023). Thanks @hmbj, @phprus, @res2k, @Baardi, @matt77hias, @waywardmonkeys, @hmbj, @yakra, @prlw1, @Arghnews, @mtillmann0, @ShifftC, @eepp, @jimmy-park and @@ -483,6 +512,10 @@ - Made `fmt::streamed` `constexpr`. (https://github.com/fmtlib/fmt/pull/3650). Thanks @muggenhor. +- Made `fmt::format_int` `constexpr` + (https://github.com/fmtlib/fmt/issues/4031, + https://github.com/fmtlib/fmt/pull/4032). Thanks @dixlorenz. + - Enabled `consteval` on older versions of MSVC (https://github.com/fmtlib/fmt/pull/3757). Thanks @phprus. diff --git a/include/fmt/base.h b/include/fmt/base.h index 2ba23d5a51b59..ceb9845591ebe 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -12,7 +12,6 @@ # define FMT_MODULE #endif -// c headers are preferable for performance reasons #ifndef FMT_MODULE # include // CHAR_BIT # include // FILE @@ -24,7 +23,7 @@ #endif // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 100202 +#define FMT_VERSION 110001 // Detect compiler versions. #if defined(__clang__) && !defined(__ibmxl__) @@ -263,7 +262,7 @@ #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - inline namespace v10 { + inline namespace v11 { # define FMT_END_NAMESPACE \ } \ } @@ -442,7 +441,8 @@ struct is_std_string_like : std::false_type {}; template struct is_std_string_like().find_first_of( typename T::value_type(), 0))>> - : std::true_type {}; + : std::is_convertible().data()), + const typename T::value_type*> {}; // Returns true iff the literal encoding is UTF-8. constexpr auto is_utf8_enabled() -> bool { @@ -1761,7 +1761,7 @@ template class basic_format_arg { * `vis(value)` will be called with the value of type `double`. */ template - FMT_CONSTEXPR auto visit(Visitor&& vis) -> decltype(vis(0)) { + FMT_CONSTEXPR auto visit(Visitor&& vis) const -> decltype(vis(0)) { switch (type_) { case detail::type::none_type: break; diff --git a/include/fmt/printf.h b/include/fmt/printf.h index ee4b06fd5e724..072cc6b309d46 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -569,8 +569,8 @@ using wprintf_args = basic_format_args; /// arguments and can be implicitly converted to `printf_args`. template inline auto make_printf_args(T&... args) - -> decltype(make_format_args>(args...)) { - return make_format_args>(args...); + -> decltype(fmt::make_format_args>(args...)) { + return fmt::make_format_args>(args...); } template struct vprintf_args { diff --git a/include/fmt/std.h b/include/fmt/std.h index 01a61cee61fbd..fb43940bc06f2 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -291,7 +291,7 @@ struct formatter, Char, if (value.has_value()) { out = detail::write(out, "expected("); - out = detail::write_escaped_alternative(out, value.value()); + out = detail::write_escaped_alternative(out, *value); } else { out = detail::write(out, "unexpected("); out = detail::write_escaped_alternative(out, value.error()); @@ -631,33 +631,67 @@ struct formatter : formatter { #endif // __cpp_lib_atomic_flag_test FMT_EXPORT -template -struct formatter, Char> : nested_formatter { +template struct formatter, Char> { private: - // Functor because C++11 doesn't support generic lambdas. - struct writer { - const formatter, Char>* f; - const std::complex& c; - - template - FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { - if (c.real() != 0) { - auto format_full = detail::string_literal{}; - return fmt::format_to(out, basic_string_view(format_full), - f->nested(c.real()), f->nested(c.imag())); - } - auto format_imag = detail::string_literal{}; - return fmt::format_to(out, basic_string_view(format_imag), - f->nested(c.imag())); + detail::dynamic_format_specs specs_; + + template + FMT_CONSTEXPR auto do_format(const std::complex& c, + detail::dynamic_format_specs& specs, + FormatContext& ctx, OutputIt out) const + -> OutputIt { + if (c.real() != 0) { + *out++ = Char('('); + out = detail::write(out, c.real(), specs, ctx.locale()); + specs.sign = sign::plus; + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + *out++ = Char(')'); + return out; } - }; + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + return out; + } public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type_constant::value); + } + template - auto format(const std::complex& c, FormatContext& ctx) const + auto format(const std::complex& c, FormatContext& ctx) const -> decltype(ctx.out()) { - return this->write_padded(ctx, writer{this, c}); + auto specs = specs_; + if (specs.width_ref.kind != detail::arg_id_kind::none || + specs.precision_ref.kind != detail::arg_id_kind::none) { + detail::handle_dynamic_spec(specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec( + specs.precision, specs.precision_ref, ctx); + } + + if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); + auto buf = basic_memory_buffer(); + + auto outer_specs = format_specs(); + outer_specs.width = specs.width; + outer_specs.fill = specs.fill; + outer_specs.align = specs.align; + + specs.width = 0; + specs.fill = {}; + specs.align = align::none; + + do_format(c, specs, ctx, basic_appender(buf)); + return detail::write(ctx.out(), + basic_string_view(buf.data(), buf.size()), + outer_specs); } }; diff --git a/support/Vagrantfile b/support/Vagrantfile index 9680a1a184abb..9d0ff6a531024 100644 --- a/support/Vagrantfile +++ b/support/Vagrantfile @@ -3,10 +3,9 @@ # A vagrant config for testing against gcc-4.8. Vagrant.configure("2") do |config| - config.vm.box = "ubuntu/xenial64" - config.disksize.size = '15GB' + config.vm.box = "bento/ubuntu-22.04-arm64" - config.vm.provider "virtualbox" do |vb| + config.vm.provider "vmware_desktop" do |vb| vb.memory = "4096" end diff --git a/support/manage.py b/support/manage.py index 41a39c47cf3c8..3467afb0f678e 100755 --- a/support/manage.py +++ b/support/manage.py @@ -14,7 +14,6 @@ import datetime, docopt, errno, fileinput, json, os import re, requests, shutil, sys from contextlib import contextmanager -from distutils.version import LooseVersion from subprocess import check_call @@ -76,147 +75,42 @@ def create_build_env(): class Env: pass env = Env() - - # Import the documentation build module. env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - sys.path.insert(0, os.path.join(env.fmt_dir, 'doc')) - import build - env.build_dir = 'build' - env.versions = build.versions - - # Virtualenv and repos are cached to speed up builds. - build.create_build_env(os.path.join(env.build_dir, 'virtualenv')) - env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) return env -@contextmanager -def rewrite(filename): - class Buffer: - pass - buffer = Buffer() - if not os.path.exists(filename): - buffer.data = '' - yield buffer - return - with open(filename) as f: - buffer.data = f.read() - yield buffer - with open(filename, 'w') as f: - f.write(buffer.data) - - fmt_repo_url = 'git@github.com:fmtlib/fmt' def update_site(env): env.fmt_repo.update(fmt_repo_url) - doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io')) - doc_repo.update('git@github.com:fmtlib/fmtlib.github.io') - - for version in env.versions: - clean_checkout(env.fmt_repo, version) - target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') - # Remove the old theme. - for entry in os.listdir(target_doc_dir): - path = os.path.join(target_doc_dir, entry) - if os.path.isdir(path): - shutil.rmtree(path) - # Copy the new theme. - for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap', - 'conf.py', 'fmt.less']: - src = os.path.join(env.fmt_dir, 'doc', entry) - dst = os.path.join(target_doc_dir, entry) - copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile - copy(src, dst) - # Rename index to contents. - contents = os.path.join(target_doc_dir, 'contents.rst') - if not os.path.exists(contents): - os.rename(os.path.join(target_doc_dir, 'index.rst'), contents) - # Fix issues in reference.rst/api.rst. - for filename in ['reference.rst', 'api.rst', 'index.rst']: - pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M) - with rewrite(os.path.join(target_doc_dir, filename)) as b: - b.data = b.data.replace('std::ostream &', 'std::ostream&') - b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data) - b.data = b.data.replace('std::FILE*', 'std::FILE *') - b.data = b.data.replace('unsigned int', 'unsigned') - #b.data = b.data.replace('operator""_', 'operator"" _') - b.data = b.data.replace( - 'format_to_n(OutputIt, size_t, string_view, Args&&', - 'format_to_n(OutputIt, size_t, const S&, const Args&') - b.data = b.data.replace( - 'format_to_n(OutputIt, std::size_t, string_view, Args&&', - 'format_to_n(OutputIt, std::size_t, const S&, const Args&') - if version == ('3.0.2'): - b.data = b.data.replace( - 'fprintf(std::ostream&', 'fprintf(std::ostream &') - if version == ('5.3.0'): - b.data = b.data.replace( - 'format_to(OutputIt, const S&, const Args&...)', - 'format_to(OutputIt, const S &, const Args &...)') - if version.startswith('5.') or version.startswith('6.'): - b.data = b.data.replace(', size_t', ', std::size_t') - if version.startswith('7.'): - b.data = b.data.replace(', std::size_t', ', size_t') - b.data = b.data.replace('join(It, It', 'join(It, Sentinel') - if version.startswith('7.1.'): - b.data = b.data.replace(', std::size_t', ', size_t') - b.data = b.data.replace('join(It, It', 'join(It, Sentinel') - b.data = b.data.replace( - 'fmt::format_to(OutputIt, const S&, Args&&...)', - 'fmt::format_to(OutputIt, const S&, Args&&...) -> ' + - 'typename std::enable_if::type') - b.data = b.data.replace('aa long', 'a long') - b.data = b.data.replace('serveral', 'several') - if version.startswith('6.2.'): - b.data = b.data.replace( - 'vformat(const S&, basic_format_args<' + - 'buffer_context>)', - 'vformat(const S&, basic_format_args<' + - 'buffer_context>>)') - # Fix a broken link in index.rst. - index = os.path.join(target_doc_dir, 'index.rst') - with rewrite(index) as b: - b.data = b.data.replace( - 'doc/latest/index.html#format-string-syntax', 'syntax.html') - # Fix issues in syntax.rst. - index = os.path.join(target_doc_dir, 'syntax.rst') - with rewrite(index) as b: - b.data = b.data.replace( - '..productionlist:: sf\n', '.. productionlist:: sf\n ') - b.data = b.data.replace('Examples:\n', 'Examples::\n') - # Build the docs. - html_dir = os.path.join(env.build_dir, 'html') - if os.path.exists(html_dir): - shutil.rmtree(html_dir) - include_dir = env.fmt_repo.dir - if LooseVersion(version) >= LooseVersion('5.0.0'): - include_dir = os.path.join(include_dir, 'include', 'fmt') - elif LooseVersion(version) >= LooseVersion('3.0.0'): - include_dir = os.path.join(include_dir, 'fmt') - import build - build.build_docs(version, doc_dir=target_doc_dir, - include_dir=include_dir, work_dir=env.build_dir) - shutil.rmtree(os.path.join(html_dir, '.doctrees')) - # Create symlinks for older versions. - for link, target in {'index': 'contents', 'api': 'reference'}.items(): - link = os.path.join(html_dir, link) + '.html' - target += '.html' - if os.path.exists(os.path.join(html_dir, target)) and \ - not os.path.exists(link): - os.symlink(target, link) - # Copy docs to the website. - version_doc_dir = os.path.join(doc_repo.dir, version) - try: - shutil.rmtree(version_doc_dir) - except OSError as e: - if e.errno != errno.ENOENT: - raise - shutil.move(html_dir, version_doc_dir) + doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev')) + doc_repo.update('git@github.com:fmtlib/fmt.dev') + + version = '11.0.0' + clean_checkout(env.fmt_repo, version) + target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') + + # Build the docs. + html_dir = os.path.join(env.build_dir, 'html') + if os.path.exists(html_dir): + shutil.rmtree(html_dir) + include_dir = env.fmt_repo.dir + import build + build.build_docs(version, doc_dir=target_doc_dir, + include_dir=include_dir, work_dir=env.build_dir) + shutil.rmtree(os.path.join(html_dir, '.doctrees')) + # Copy docs to the website. + version_doc_dir = os.path.join(doc_repo.dir, version) + try: + shutil.rmtree(version_doc_dir) + except OSError as e: + if e.errno != errno.ENOENT: + raise + shutil.move(html_dir, version_doc_dir) def release(args): @@ -250,6 +144,18 @@ def release(args): if first_section[0] == '\n': first_section.pop(0) + ns_version = None + base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h') + for line in fileinput.input(base_h_path): + m = re.match(r'\s*inline namespace v(.*) .*', line) + if m: + ns_version = m.group(1) + break + major_version = version.split('.')[0] + if not ns_version or ns_version != major_version: + raise Exception(f'Version mismatch {ns_version} != {major_version}') + + # Workaround GitHub-flavored Markdown treating newlines as
. changes = '' code_block = False stripped = False @@ -262,36 +168,19 @@ def release(args): if code_block: changes += line continue - if line == '\n': - changes += line + if line == '\n' or re.match(r'^\s*\|.*', line): if stripped: - changes += line + changes += '\n' stripped = False + changes += line continue if stripped: line = ' ' + line.lstrip() changes += line.rstrip() stripped = True - cmakelists = 'CMakeLists.txt' - for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists), - inplace=True): - prefix = 'set(FMT_VERSION ' - if line.startswith(prefix): - line = prefix + version + ')\n' - sys.stdout.write(line) - - # Add the version to the build script. - script = os.path.join('doc', 'build.py') - script_path = os.path.join(fmt_repo.dir, script) - for line in fileinput.input(script_path, inplace=True): - m = re.match(r'( *versions \+= )\[(.+)\]', line) - if m: - line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version) - sys.stdout.write(line) - fmt_repo.checkout('-B', 'release') - fmt_repo.add(changelog, cmakelists, script) + fmt_repo.add(changelog) fmt_repo.commit('-m', 'Update version') # Build the docs and package. diff --git a/support/python/mkdocstrings_handlers/cxx/__init__.py b/support/python/mkdocstrings_handlers/cxx/__init__.py index b3d91bf8f5be4..4ade52ab223d8 100644 --- a/support/python/mkdocstrings_handlers/cxx/__init__.py +++ b/support/python/mkdocstrings_handlers/cxx/__init__.py @@ -158,13 +158,15 @@ def __init__(self, **kwargs: Any) -> None: # Run doxygen. cmd = ['doxygen', '-'] - doc_dir = Path(__file__).parents[3] - top_dir = os.path.dirname(doc_dir) + support_dir = Path(__file__).parents[3] + top_dir = os.path.dirname(support_dir) include_dir = os.path.join(top_dir, 'include', 'fmt') self._ns2doxyxml = {} - self._doxyxml_dir = os.path.join(top_dir, 'build', 'doxyxml') + build_dir = os.path.join(top_dir, 'build') + os.makedirs(build_dir, exist_ok=True) + self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) - out, _ = p.communicate(input=r''' + _, _ = p.communicate(input=r''' PROJECT_NAME = fmt GENERATE_XML = YES GENERATE_LATEX = NO @@ -188,7 +190,7 @@ def __init__(self, **kwargs: Any) -> None: ' '.join([os.path.join(include_dir, h) for h in headers]), self._doxyxml_dir).encode('utf-8')) if p.returncode != 0: - raise CalledProcessError(p.returncode, cmd) + raise CalledProcessError(p.returncode, cmd) # Merge all file-level XMLs into one to simplify search. self._file_doxyxml = None diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f7214a8ff89c..fac82392e439a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,6 +45,10 @@ function(add_fmt_test name) add_fmt_executable(${name} ${sources}) target_link_libraries(${name} ${libs}) + if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE) + target_compile_definitions(${name} PUBLIC FMT_UNICODE=0) + endif () + # Define if certain C++ features can be used. if (FMT_PEDANTIC) target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS}) diff --git a/test/format-test.cc b/test/format-test.cc index 257b6ead7a8f7..a9ef19fc19c81 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2420,3 +2420,25 @@ TEST(format_test, formatter_overrides_implicit_conversion) { EXPECT_EQ(fmt::format("{}", convertible_to_int()), "x"); EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "y"); } + +struct ustring { + using value_type = unsigned; + + auto find_first_of(value_type, size_t) const -> size_t; + + auto data() const -> const char*; + auto size() const -> size_t; +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter : formatter { + auto format(const ustring&, format_context& ctx) const + -> decltype(ctx.out()) { + return formatter::format("ustring", ctx); + } +}; +FMT_END_NAMESPACE + +TEST(format_test, ustring) { + EXPECT_EQ(fmt::format("{}", ustring()), "ustring"); +} diff --git a/test/printf-test.cc b/test/printf-test.cc index d3f03b7f3855b..ee5cf0b0e0a11 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -6,6 +6,10 @@ // For the license information refer to format.h. #include "fmt/printf.h" +// include if possible for https://github.com/fmtlib/fmt/pull/4042 +#if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L +# include +#endif #include #include diff --git a/test/std-test.cc b/test/std-test.cc index 17c95cfe57ed2..1327fe8279fd2 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -66,10 +66,34 @@ TEST(std_test, thread_id) { } TEST(std_test, complex) { + using limits = std::numeric_limits; + EXPECT_EQ(fmt::format("{}", std::complex(1, limits::quiet_NaN())), + "(1+nan i)"); + EXPECT_EQ(fmt::format("{}", std::complex(1, -limits::infinity())), + "(1-inf i)"); + + EXPECT_EQ(fmt::format("{}", std::complex(1, 2)), "(1+2i)"); + EXPECT_EQ(fmt::format("{}", std::complex(1, 2.2)), "(1+2.2i)"); + EXPECT_EQ(fmt::format("{}", std::complex(1, -2.2)), "(1-2.2i)"); EXPECT_EQ(fmt::format("{}", std::complex(0, 2.2)), "2.2i"); + EXPECT_EQ(fmt::format("{}", std::complex(0, -2.2)), "-2.2i"); + + EXPECT_EQ(fmt::format("{:+}", std::complex(0, 2.2)), "+2.2i"); + EXPECT_EQ(fmt::format("{:+}", std::complex(0, -2.2)), "-2.2i"); + EXPECT_EQ(fmt::format("{:+}", std::complex(1, -2.2)), "(+1-2.2i)"); + EXPECT_EQ(fmt::format("{:+}", std::complex(1, 2.2)), "(+1+2.2i)"); + EXPECT_EQ(fmt::format("{: }", std::complex(1, 2.2)), "( 1+2.2i)"); + EXPECT_EQ(fmt::format("{: }", std::complex(1, -2.2)), "( 1-2.2i)"); + EXPECT_EQ(fmt::format("{:>20.2f}", std::complex(1, 2.2)), " (1.00+2.20i)"); + EXPECT_EQ(fmt::format("{:<20.2f}", std::complex(1, 2.2)), + "(1.00+2.20i) "); + EXPECT_EQ(fmt::format("{:<20.2f}", std::complex(1, -2.2)), + "(1.00-2.20i) "); + EXPECT_EQ(fmt::format("{:<{}.{}f}", std::complex(1, -2.2), 20, 2), + "(1.00-2.20i) "); } #ifdef __cpp_lib_source_location