diff --git a/lmorpho/morphio.cpp b/lmorpho/morphio.cpp index 4c50fb6b77178e3466ae2937dd40b13b18eaaccd..47fc16f7b9313ef26964f13a1557b4fbde0e44f1 100644 --- a/lmorpho/morphio.cpp +++ b/lmorpho/morphio.cpp @@ -6,32 +6,15 @@ #include <morphology.hpp> #include <swcio.hpp> +#include <util/strprintf.hpp> #include "morphio.hpp" using nest::mc::io::swc_record; +using nest::mc::util::strprintf; std::vector<swc_record> as_swc(const nest::mc::morphology& morph); -// printf wrappers. - -template <typename... Args> -static std::string strprintf(const char* fmt, Args&&... args) { - thread_local static std::vector<char> buffer(1024); - - for (;;) { - int n = std::snprintf(buffer.data(), buffer.size(), fmt, std::forward<Args>(args)...); - if (n<0) return ""; // error - if ((unsigned)n<buffer.size()) return std::string(buffer.data()); - buffer.resize(2*n); - } -} - -template <typename... Args> -static std::string strprintf(std::string fmt, Args&&... args) { - return strprintf(fmt.c_str(), std::forward<Args>(args)...); -} - // Multi-file manager implementation. multi_file::multi_file(const std::string& pattern, int digits) { auto npos = std::string::npos; diff --git a/src/util/strprintf.hpp b/src/util/strprintf.hpp new file mode 100644 index 0000000000000000000000000000000000000000..218d38bc2a21871197aa2dddaad32ce65386cf34 --- /dev/null +++ b/src/util/strprintf.hpp @@ -0,0 +1,52 @@ +#pragma once + +/* Thin wrapper around std::snprintf for sprintf-like formatting + * to std::string. */ + +#include <cstdio> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +namespace nest { +namespace mc { +namespace util { + +namespace impl { + template <typename X> + X sprintf_arg_translate(const X& x) { return x; } + + inline const char* sprintf_arg_translate(const std::string& x) { return x.c_str(); } + + template <typename T, typename Deleter> + T* sprintf_arg_translate(const std::unique_ptr<T, Deleter>& x) { return x.get(); } + + template <typename T> + T* sprintf_arg_translate(const std::shared_ptr<T>& x) { return x.get(); } +} + +template <typename... Args> +std::string strprintf(const char* fmt, Args&&... args) { + thread_local static std::vector<char> buffer(1024); + + for (;;) { + int n = std::snprintf(buffer.data(), buffer.size(), fmt, impl::sprintf_arg_translate(std::forward<Args>(args))...); + if (n<0) { + throw std::system_error(errno, std::generic_category()); + } + else if ((unsigned)n<buffer.size()) { + return std::string(buffer.data(), n); + } + buffer.resize(2*n); + } +} + +template <typename... Args> +std::string strprintf(const std::string& fmt, Args&&... args) { + return strprintf(fmt.c_str(), std::forward<Args>(args)...); +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index eb2170e5e7601a77b862132124fa9a51e2ba7488..2aa34ffb8987dfb36b48f1c7bd29e4f7314d782e 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -60,6 +60,7 @@ set(TEST_SOURCES test_spikes.cpp test_spike_store.cpp test_stimulus.cpp + test_strprintf.cpp test_swcio.cpp test_synapses.cpp test_tree.cpp diff --git a/tests/unit/test_strprintf.cpp b/tests/unit/test_strprintf.cpp new file mode 100644 index 0000000000000000000000000000000000000000..781ea1b0f446f711b62981587a23282a1c91d0ec --- /dev/null +++ b/tests/unit/test_strprintf.cpp @@ -0,0 +1,63 @@ +#include <cstdio> +#include <memory> +#include <string> + +#include "../gtest.h" +#include <util/strprintf.hpp> + +using namespace nest::mc::util; + + +TEST(strprintf, simple) { + char buf[200]; + + const char* fmt1 = " %% %04d % 3.2f %#016x %c"; + sprintf(buf, fmt1, 3, 7.1e-3, 0x1234ul, 'x'); + auto result = strprintf(fmt1, 3, 7.1e-3, 0x1234ul, 'x'); + EXPECT_EQ(std::string(buf), result); + + const char* fmt2 = "%5s %3s"; + sprintf(buf, fmt2, "bear", "pear"); + result = strprintf(fmt2, "bear", "pear"); + EXPECT_EQ(std::string(buf), result); +} + +TEST(strprintf, longstr) { + std::string aaaa(2000, 'a'); + std::string bbbb(2000, 'b'); + std::string x; + + x = aaaa; + ASSERT_EQ(x, strprintf("%s", x.c_str())); + + x += bbbb+x; + ASSERT_EQ(x, strprintf("%s", x.c_str())); + + x += bbbb+x; + ASSERT_EQ(x, strprintf("%s", x.c_str())); + + x += bbbb+x; + ASSERT_EQ(x, strprintf("%s", x.c_str())); + + x += bbbb+x; + ASSERT_EQ(x, strprintf("%s", x.c_str())); +} + +TEST(strprintf, wrappers) { + // can we print strings and smart-pointers directly? + + char buf[200]; + + auto uptr = std::unique_ptr<int>{new int(17)}; + sprintf(buf, "uptr %p", uptr.get()); + + EXPECT_EQ(std::string(buf), strprintf("uptr %p", uptr)); + + auto sptr = std::shared_ptr<double>{new double(19.)}; + sprintf(buf, "sptr %p", sptr.get()); + + EXPECT_EQ(std::string(buf), strprintf("sptr %p", sptr)); + + EXPECT_EQ(std::string("fish"), strprintf("fi%s", std::string("sh"))); +} +