aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.util
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-29 00:42:31 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-29 00:42:31 +0100
commitd24315268c11d435bb9accbce87b7f46dda6ed3e (patch)
tree66589cb6a15fa74d4b09683105c583e4a5c222b4 /nihil.util
parent7741a9698d29f79aca3e47495dcdf87c7a712f42 (diff)
downloadnihil-d24315268c11d435bb9accbce87b7f46dda6ed3e.tar.gz
nihil-d24315268c11d435bb9accbce87b7f46dda6ed3e.tar.bz2
cli: improve command dispatch a bit
Diffstat (limited to 'nihil.util')
-rw-r--r--nihil.util/CMakeLists.txt2
-rw-r--r--nihil.util/capture_stream.ccm61
-rw-r--r--nihil.util/nihil.util.ccm1
-rw-r--r--nihil.util/test_capture_stream.cc44
4 files changed, 108 insertions, 0 deletions
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 <iostream>
+
+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<typename Char, typename Traits>
+struct capture_stream {
+ capture_stream(std::basic_ostream<Char, Traits> &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<Char, Traits>
+ {
+ return self.m_buffer.view();
+ }
+
+private:
+ std::basic_ostringstream<Char, Traits> m_buffer;
+ std::basic_ostream<Char, Traits> *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 <iostream>
+
+#include <catch2/catch_test_macros.hpp>
+
+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");
+ }
+}