-
Notifications
You must be signed in to change notification settings - Fork 27
Feature/add libfmt #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Feature/add libfmt #182
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
set( ECKIT_LIBRARIES @ECKIT_LIBRARIES@ ) | ||
|
||
include( CMakeFindDependencyMacro ) | ||
|
||
find_dependency(fmt REQUIRED HINTS ${CMAKE_CURRENT_LIST_DIR}/../eckit/third-party/fmt-11.1.4 @fmt_DIR@) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#include "Format.h" | ||
|
||
namespace eckit { | ||
|
||
std::ostream_iterator<const char&> makeOrForwardOutputiterator(std::ostream& os) { | ||
return std::ostream_iterator<const char&>(os); | ||
} | ||
|
||
} // namespace eckit |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,109 @@ | ||||||
/* | ||||||
* (C) Copyright 2025- ECMWF. | ||||||
* | ||||||
* This software is licensed under the terms of the Apache Licence Version 2.0 | ||||||
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. | ||||||
* In applying this licence, ECMWF does not waive the privileges and immunities | ||||||
* granted to it by virtue of its status as an intergovernmental organisation nor | ||||||
* does it submit to any jurisdiction. | ||||||
*/ | ||||||
#pragma once | ||||||
|
||||||
#include <fmt/chrono.h> | ||||||
#include <fmt/compile.h> | ||||||
#include <fmt/format.h> | ||||||
#include <fmt/ranges.h> | ||||||
#include <fmt/std.h> | ||||||
|
||||||
#include <iterator> | ||||||
#include <type_traits> | ||||||
|
||||||
/* | ||||||
* Format wrappers around libfmt | ||||||
* | ||||||
* For most cases it is encouraged to use the macro `eckit_format`. It will perform compile time checks. | ||||||
* | ||||||
* For a very specific timecritical cases `eckit_format_cc` can be used to produce very optimized formatting code. | ||||||
* Disadvantag: more binary code | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* | ||||||
* For other cases where the format string may dynamicall be configured somethere else, the functions `eckit::format` | ||||||
* and `eckit::format_to` can be used - here checks are performed at runtime and may throw. | ||||||
*/ | ||||||
|
||||||
|
||||||
#define ENABLE_FORMAT(typ) \ | ||||||
template <> \ | ||||||
struct fmt::formatter<typ> : fmt::ostream_formatter {} | ||||||
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This macro should not be so generic but be prefixed with |
||||||
|
||||||
/// Format a string with compile time checks. | ||||||
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax. | ||||||
/// Must be known at compiletime | ||||||
/// @param ... args to be upplied into formatString | ||||||
#define eckit_format(str, ...) fmt::format(FMT_STRING(str), ##__VA_ARGS__) | ||||||
|
||||||
/// Format s string with compile time optimizations. | ||||||
/// Converts formatString into a format string that will be parsed at compile time and converted into efficient | ||||||
/// formatting code. | ||||||
/// @note Format string compilation can generate more binary code compared to the default API and is only recommended in | ||||||
/// places where formatting is a performance bottleneck. | ||||||
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax. | ||||||
/// @param ... args to be upplied into formatString | ||||||
/// @throws fm::format_error if args cannot be applied to formatString or formatString syntax is invalid. | ||||||
#define eckit_format_cc(str, ...) fmt::format(FMT_COMPILE(str), ##__VA_ARGS__) | ||||||
|
||||||
|
||||||
/// Format a string with compile time checks and output to an outputiterator or ostream. | ||||||
/// @param out OutputIterator or ostream | ||||||
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax. | ||||||
/// Must be known at compiletime | ||||||
/// @param ... args to be upplied into formatString | ||||||
#define eckit_format_to(out, str, ...) \ | ||||||
fmt::format_to(eckit::makeOrForwardOutputiterator(out), FMT_STRING(str), ##__VA_ARGS__) | ||||||
|
||||||
/// Format s string with compile time optimizations and output to an outputiterator or ostream. | ||||||
/// Converts formatString into a format string that will be parsed at compile time and converted into efficient | ||||||
/// formatting code. | ||||||
/// @note Format string compilation can generate more binary code compared to the default API and is only recommended in | ||||||
/// places where formatting is a performance bottleneck. | ||||||
/// @param out OutputIterator or ostream | ||||||
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax. | ||||||
/// @param ... args to be upplied into formatString | ||||||
/// @throws fm::format_error if args cannot be applied to formatString or formatString syntax is invalid. | ||||||
#define eckit_format_to_cc(out, str, ...) \ | ||||||
fmt::format_to(eckit::makeOrForwardOutputiterator(out), FMT_COMPILE(str), ##__VA_ARGS__) | ||||||
|
||||||
namespace eckit { | ||||||
|
||||||
template <typename OutputIt, std::enable_if_t<!std::is_base_of_v<std::ostream, std::decay_t<OutputIt>>, bool> = true> | ||||||
OutputIt&& makeOrForwardOutputiterator(OutputIt&& outputIt) { | ||||||
return std::forward<OutputIt>(outputIt); | ||||||
} | ||||||
|
||||||
std::ostream_iterator<const char&> makeOrForwardOutputiterator(std::ostream& os); | ||||||
|
||||||
|
||||||
/// Format a string with compile time checks. | ||||||
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax. | ||||||
/// Must be known at compiletime | ||||||
/// @param ... args to be upplied into formatString | ||||||
/// @throws fmt::format_error | ||||||
template <typename StringType, typename... Args> | ||||||
std::string str_format(StringType&& formatString, Args&&... args) { | ||||||
return fmt::format(fmt::runtime(std::forward<StringType>(formatString)), std::forward<Args>(args)...); | ||||||
} | ||||||
|
||||||
|
||||||
/// Format a string with compile time checks to an output iterator | ||||||
/// @param outputIt output iterator or ostream to write to | ||||||
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax. | ||||||
/// Must be known at compiletime | ||||||
/// @param ... args to be upplied into formatString | ||||||
/// @throws fmt::format_error | ||||||
template <typename OutputIt, typename StringType, typename... Args> | ||||||
void str_format_to(OutputIt&& outputIt, StringType&& formatString, Args&&... args) { | ||||||
fmt::format_to(makeOrForwardOutputiterator(std::forward<OutputIt>(outputIt)), | ||||||
fmt::runtime(std::forward<StringType>(formatString)), std::forward<Args>(args)...); | ||||||
} | ||||||
|
||||||
|
||||||
} // namespace eckit |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,23 @@ | ||
// #include <cmath> | ||
|
||
// Some of the math.h/cmath functions are not clean when switching to C++11 | ||
#if __cplusplus <= 199711L | ||
#include <cmath> | ||
#else | ||
#include <cmath> | ||
#define fpclassify(x) std::fpclassify((x)) | ||
#define isinf(x) std::isinf((x)) | ||
#define isnan(x) std::isnan((x)) | ||
#define signbit(x) std::signbit((x)) | ||
#endif | ||
|
||
#include <sys/types.h> | ||
#include <cmath> | ||
#include <limits> | ||
|
||
#include "eckit/exception/Exceptions.h" | ||
#include "eckit/types/FloatCompare.h" | ||
|
||
using std::fpclassify; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems unrelated. We weren't using the std:: ones as we had issues on some compiler combinations. Not sure I want to couple this change in this context? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we included the Here some macros to math functions were used to avoid having to prefix In that sense using the |
||
using std::isinf; | ||
using std::isnan; | ||
using std::signbit; | ||
|
||
namespace eckit::types { | ||
|
||
//---------------------------------------------------------------------------------------------------------------------- | ||
|
||
namespace detail { | ||
|
||
// FIXME: The following functions are available in std:: as of C++11: | ||
// * fpclassify | ||
// * isinf | ||
// * isnan | ||
// * signbit | ||
// For the moment we have to use the (non namespaced) versions from math.h | ||
|
||
template <class T> | ||
inline T abs(T); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
ecbuild_add_test( TARGET eckit_test_format | ||
SOURCES test_format.cc | ||
LIBS eckit ) | ||
|
||
ecbuild_add_test( TARGET eckit_test_format_to | ||
SOURCES test_format_to.cc | ||
LIBS eckit ) | ||
|
||
ecbuild_add_test( TARGET eckit_test_format_log | ||
SOURCES test_format_log.cc | ||
LIBS eckit ) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* (C) Copyright 2025- ECMWF. | ||
* | ||
* This software is licensed under the terms of the Apache Licence Version 2.0 | ||
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. | ||
* In applying this licence, ECMWF does not waive the privileges and immunities | ||
* granted to it by virtue of its status as an intergovernmental organisation nor | ||
* does it submit to any jurisdiction. | ||
*/ | ||
#include <eckit/testing/Test.h> | ||
|
||
#include "eckit/format/Format.h" | ||
|
||
CASE("Can use eckit_format macro") { | ||
EXPECT_EQUAL(eckit_format("Hello {} {}", 1, 2), std::string("Hello 1 2")); | ||
EXPECT_EQUAL(eckit_format(std::string_view("Hello {} {}"), 1, 2), std::string("Hello 1 2")); | ||
} | ||
|
||
CASE("Can use eckit::str_format") { | ||
EXPECT_EQUAL(eckit::str_format("Hello {} {}", 1, 2), std::string("Hello 1 2")); | ||
EXPECT_EQUAL(eckit::str_format(std::string_view("Hello {} {}"), 1, 2), std::string("Hello 1 2")); | ||
EXPECT_EQUAL(eckit::str_format(std::string("Hello {} {}"), 1, 2), std::string("Hello 1 2")); | ||
std::string str("Hello {} {}"); | ||
std::string_view str_v(str); | ||
EXPECT_EQUAL(eckit::str_format(str_v, 1, 2), std::string("Hello 1 2")); | ||
} | ||
|
||
CASE("eckit::str_format throws on format erors") { | ||
EXPECT_THROWS_AS(eckit::str_format("Hello {} {} {}", 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format(std::string("Hello {} {} {}"), 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format(std::string_view("Hello {} {} {}"), 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format("Hello {} {} {", 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format(std::string("Hello {} {} {"), 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format(std::string_view("Hello {} {} {"), 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format("Hello {:p} {} {}", 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format(std::string("Hello {:p} {} {}"), 1, 2), fmt::format_error); | ||
EXPECT_THROWS_AS(eckit::str_format(std::string_view("Hello {:p} {} {}"), 1, 2), fmt::format_error); | ||
} | ||
|
||
struct MyType { | ||
int x{5}; | ||
}; | ||
std::ostream& operator<<(std::ostream& out, const MyType& mt) { | ||
return out << "MyType{" << mt.x << "}"; | ||
} | ||
ENABLE_FORMAT(MyType); | ||
|
||
CASE("ENABLE_FORMAT works") { | ||
EXPECT_EQUAL(eckit::str_format("Hello {}", MyType{}), std::string("Hello MyType{5}")); | ||
} | ||
|
||
ENABLE_FORMAT(eckit::CodeLocation); | ||
CASE("ENABLE_FORMAT CodeLocation") { | ||
// This is just an example | ||
std::cout << eckit::str_format("{}", Here()); | ||
} | ||
|
||
int main(int argc, char** argv) { | ||
return eckit::testing::run_tests(argc, argv); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we creating a second directory here. I don't see libfmt as a dependency being conceptually meaningfully different to xxHash. They are both third party contribs. Please put in the 'original' location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, it is a third-party library ... same for xxHash. The naming
contribution
is just not appropriate for these because they are no contributions from externals provided just for this purpose.Also note that in #187 it is to discussed to move them to
third_party
directory as well, and also use git submodules properly...However, we want to keep the discussion with the git submodules separate. That's why this library is still vendored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semantics... contrib is the directory for external contribution; personally I never heard of the term "vendored" before in this context either.
If instead this is agreed to go to a new directory third-party, then I propose to also move xxHash to this third-party to be consistent at least.