From d24315268c11d435bb9accbce87b7f46dda6ed3e Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 29 Jun 2025 00:42:31 +0100 Subject: cli: improve command dispatch a bit --- nihil.util/CMakeLists.txt | 2 ++ nihil.util/capture_stream.ccm | 61 +++++++++++++++++++++++++++++++++++++++ nihil.util/nihil.util.ccm | 1 + nihil.util/test_capture_stream.cc | 44 ++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 nihil.util/capture_stream.ccm create mode 100644 nihil.util/test_capture_stream.cc (limited to 'nihil.util') diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt index b809a68..a07ea7d 100644 --- a/nihil.util/CMakeLists.txt +++ b/nihil.util/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(nihil.util PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.util.ccm + capture_stream.ccm ctype.ccm parse_size.ccm next_word.ccm @@ -17,6 +18,7 @@ if(NIHIL_TESTS) enable_testing() add_executable(nihil.util.test + test_capture_stream.cc test_ctype.cc test_parse_size.cc test_next_word.cc diff --git a/nihil.util/capture_stream.ccm b/nihil.util/capture_stream.ccm new file mode 100644 index 0000000..3333505 --- /dev/null +++ b/nihil.util/capture_stream.ccm @@ -0,0 +1,61 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include + +export module nihil.util:capture_stream; + +namespace nihil { + +/* + * Capture output written to a stream and redirect it to an internal string + * buffer. Call .str() to get the data written. Call .release() to stop + * capturing (or simply delete the capture_stream object). + */ +export template +struct capture_stream { + capture_stream(std::basic_ostream &stream) + : m_stream(&stream) + { + m_old_streambuf = m_stream->rdbuf(); + m_stream->rdbuf(m_buffer.rdbuf()); + } + + ~capture_stream() { + if (m_old_streambuf == nullptr) + return; + release(); + } + + /* + * Release this capture, returning the stream to its previous state. + */ + auto release(this capture_stream &self) -> void + { + if (self.m_old_streambuf == nullptr) + throw std::logic_error( + "release() called on empty capture_stream"); + + self.m_stream->rdbuf(self.m_old_streambuf); + self.m_old_streambuf = nullptr; + } + + /* + * Get the data which has been written to the stream. + */ + [[nodiscard]] auto str(this capture_stream const &self) + -> std::basic_string_view + { + return self.m_buffer.view(); + } + +private: + std::basic_ostringstream m_buffer; + std::basic_ostream *m_stream; + std::streambuf *m_old_streambuf; +}; + +} // namespace nihil diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm index afd513a..89510c9 100644 --- a/nihil.util/nihil.util.ccm +++ b/nihil.util/nihil.util.ccm @@ -6,6 +6,7 @@ module; export module nihil.util; +export import :capture_stream; export import :ctype; export import :parse_size; export import :next_word; diff --git a/nihil.util/test_capture_stream.cc b/nihil.util/test_capture_stream.cc new file mode 100644 index 0000000..27c8596 --- /dev/null +++ b/nihil.util/test_capture_stream.cc @@ -0,0 +1,44 @@ +/* + * This source code is released into the public domain. + */ + +#include + +#include + +import nihil.util; + +TEST_CASE("nihil.util: capture", "[nihil][nihil.util]") +{ + SECTION("std::cout with release()") { + auto cap = nihil::capture_stream(std::cout); + + std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + + cap.release(); + REQUIRE(cap.str() == "1+1=2\n"); + } + + SECTION("std::cout with dtor") { + auto cap = nihil::capture_stream(std::cout); + std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + } + + SECTION("std::cerr with release()") { + auto cap = nihil::capture_stream(std::cerr); + + std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + + cap.release(); + REQUIRE(cap.str() == "1+1=2\n"); + } + + SECTION("std::cerr with dtor") { + auto cap = nihil::capture_stream(std::cerr); + std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + } +} -- cgit v1.2.3