Skip to content

Commit e4a62ce

Browse files
committed
Add fmt::format wrapper macros/functions
1 parent 1c336e6 commit e4a62ce

File tree

6 files changed

+132
-5
lines changed

6 files changed

+132
-5
lines changed

src/eckit/CMakeLists.txt

+11-5
Original file line numberDiff line numberDiff line change
@@ -840,15 +840,22 @@ list( APPEND eckit_testing_srcs
840840
testing/Test.h
841841
)
842842

843+
list( APPEND eckit_format_srcs
844+
format/Format.h
845+
format/Format.cc
846+
)
847+
843848
list( APPEND eckit_dirs
844849
bases
845850
compat
846851
config
847852
container
848853
exception
849854
filesystem
855+
format
850856
io
851857
log
858+
maths
852859
memory
853860
message
854861
net
@@ -857,14 +864,13 @@ list( APPEND eckit_dirs
857864
persist
858865
runtime
859866
serialisation
860-
thread
861-
transaction
862-
value
863-
maths
864867
system
865868
testing
869+
thread
870+
transaction
866871
types
867872
utils
873+
value
868874
)
869875

870876
foreach( dir ${eckit_dirs} )
@@ -945,14 +951,14 @@ ecbuild_add_library(
945951
"${CURL_LIBRARIES}"
946952
"${AIO_LIBRARIES}"
947953
"${RADOS_LIBRARIES}"
948-
fmt::fmt
949954

950955
PUBLIC_LIBS
951956
${CMATH_LIBRARIES}
952957
${RT_LIBRARIES}
953958
${THREADS_LIBRARIES}
954959
${CMAKE_DL_LIBS}
955960
${ECKIT_SYSTEM_EXTRA_LIBS}
961+
fmt::fmt
956962

957963
LINKER_LANGUAGE CXX )
958964

src/eckit/format/Format.cc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "Format.h"
2+
3+
namespace eckit {}

src/eckit/format/Format.h

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* (C) Copyright 2025- ECMWF.
3+
*
4+
* This software is licensed under the terms of the Apache Licence Version 2.0
5+
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6+
* In applying this licence, ECMWF does not waive the privileges and immunities
7+
* granted to it by virtue of its status as an intergovernmental organisation nor
8+
* does it submit to any jurisdiction.
9+
*/
10+
#pragma once
11+
12+
#include <fmt/chrono.h>
13+
#include <fmt/compile.h>
14+
#include <fmt/format.h>
15+
#include <fmt/ranges.h>
16+
#include <fmt/std.h>
17+
18+
#define ENABLE_FORMAT(typ) \
19+
template <> \
20+
struct fmt::formatter<typ> : fmt::ostream_formatter {}
21+
22+
/// Format a string with compile time checks.
23+
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax.
24+
/// Must be known at compiletime
25+
/// @param ... args to be upplied into formatString
26+
#define eckit_format(str, ...) fmt::format(FMT_STRING(str), ##__VA_ARGS__)
27+
28+
/// Format s string with compile time optimizations.
29+
/// Converts formatString into a format string that will be parsed at compile time and converted into efficient
30+
/// formatting code.
31+
/// @note Format string compilation can generate more binary code compared to the default API and is only recommended in
32+
/// places where formatting is a performance bottleneck.
33+
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax.
34+
/// @param ... args to be upplied into formatString
35+
/// @throws fm::format_error if args cannot be applied to formatString or formatString syntax is invalid.
36+
#define eckit_format_cc(str, ...) fmt::format(FMT_COMPILE(str), ##__VA_ARGS__)
37+
38+
39+
namespace eckit {
40+
41+
/// Format a string with compile time checks.
42+
/// @param formatString to use, see: <https://fmt.dev/11.1/syntax/> for description of syntax.
43+
/// Must be known at compiletime
44+
/// @param ... args to be upplied into formatString
45+
/// @throws fmt::format_error
46+
template <typename StringType, typename... Args>
47+
std::string format(StringType&& formatString, Args&&... args) {
48+
return fmt::format(fmt::runtime(std::forward<StringType>(formatString)), std::forward<Args>(args)...);
49+
}
50+
51+
52+
} // namespace eckit

tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_subdirectory( config )
22
add_subdirectory( container )
33
add_subdirectory( exception )
44
add_subdirectory( filesystem )
5+
add_subdirectory( format )
56
add_subdirectory( geometry )
67
add_subdirectory( io )
78
add_subdirectory( large_file )

tests/format/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ecbuild_add_test( TARGET eckit_test_format
2+
SOURCES test_format.cc
3+
LIBS eckit )
4+

tests/format/test_format.cc

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* (C) Copyright 2025- ECMWF.
3+
*
4+
* This software is licensed under the terms of the Apache Licence Version 2.0
5+
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6+
* In applying this licence, ECMWF does not waive the privileges and immunities
7+
* granted to it by virtue of its status as an intergovernmental organisation nor
8+
* does it submit to any jurisdiction.
9+
*/
10+
#include <eckit/testing/Test.h>
11+
12+
#include "eckit/format/Format.h"
13+
14+
CASE("Can use eckit_format macro") {
15+
EXPECT_EQUAL(eckit_format("Hello {} {}", 1, 2), std::string("Hello 1 2"));
16+
EXPECT_EQUAL(eckit_format(std::string_view("Hello {} {}"), 1, 2), std::string("Hello 1 2"));
17+
}
18+
19+
CASE("Can use eckit::format") {
20+
EXPECT_EQUAL(eckit::format("Hello {} {}", 1, 2), std::string("Hello 1 2"));
21+
EXPECT_EQUAL(eckit::format(std::string_view("Hello {} {}"), 1, 2), std::string("Hello 1 2"));
22+
EXPECT_EQUAL(eckit::format(std::string("Hello {} {}"), 1, 2), std::string("Hello 1 2"));
23+
std::string str("Hello {} {}");
24+
std::string_view str_v(str);
25+
EXPECT_EQUAL(eckit::format(str_v, 1, 2), std::string("Hello 1 2"));
26+
}
27+
28+
CASE("eckit::format throws on format erors") {
29+
EXPECT_THROWS_AS(eckit::format("Hello {} {} {}", 1, 2), fmt::format_error);
30+
EXPECT_THROWS_AS(eckit::format(std::string("Hello {} {} {}"), 1, 2), fmt::format_error);
31+
EXPECT_THROWS_AS(eckit::format(std::string_view("Hello {} {} {}"), 1, 2), fmt::format_error);
32+
EXPECT_THROWS_AS(eckit::format("Hello {} {} {", 1, 2), fmt::format_error);
33+
EXPECT_THROWS_AS(eckit::format(std::string("Hello {} {} {"), 1, 2), fmt::format_error);
34+
EXPECT_THROWS_AS(eckit::format(std::string_view("Hello {} {} {"), 1, 2), fmt::format_error);
35+
EXPECT_THROWS_AS(eckit::format("Hello {:p} {} {}", 1, 2), fmt::format_error);
36+
EXPECT_THROWS_AS(eckit::format(std::string("Hello {:p} {} {}"), 1, 2), fmt::format_error);
37+
EXPECT_THROWS_AS(eckit::format(std::string_view("Hello {:p} {} {}"), 1, 2), fmt::format_error);
38+
}
39+
40+
struct MyType {
41+
int x{5};
42+
};
43+
std::ostream& operator<<(std::ostream& out, const MyType& mt) {
44+
return out << "MyType{" << mt.x << "}";
45+
}
46+
ENABLE_FORMAT(MyType);
47+
48+
CASE("ENABLE_FORMAT works") {
49+
EXPECT_EQUAL(eckit::format("Hello {}", MyType{}), std::string("Hello MyType{5}"));
50+
}
51+
52+
ENABLE_FORMAT(eckit::CodeLocation);
53+
CASE("ENABLE_FORMAT CodeLocation") {
54+
// This is just an example
55+
std::cout << eckit::format("{}", Here());
56+
}
57+
58+
59+
int main(int argc, char** argv) {
60+
return eckit::testing::run_tests(argc, argv);
61+
}

0 commit comments

Comments
 (0)