diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d44da4..8c395dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ set(HEADER_FILES io/binaryreader.h io/binarywriter.h io/bitreader.h + io/buffersearch.h io/copy.h io/inifile.h io/path.h @@ -49,6 +50,7 @@ set(SRC_FILES io/binaryreader.cpp io/binarywriter.cpp io/bitreader.cpp + io/buffersearch.cpp io/inifile.cpp io/path.cpp io/nativefilestream.cpp @@ -112,8 +114,8 @@ set(META_APP_AUTHOR "Martchus") set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "Useful C++ classes and routines such as argument parser, IO and conversion utilities") set(META_VERSION_MAJOR 5) -set(META_VERSION_MINOR 10) -set(META_VERSION_PATCH 6) +set(META_VERSION_MINOR 11) +set(META_VERSION_PATCH 0) # find required 3rd party libraries include(3rdParty) diff --git a/io/buffersearch.cpp b/io/buffersearch.cpp new file mode 100644 index 0000000..cdb5db6 --- /dev/null +++ b/io/buffersearch.cpp @@ -0,0 +1,80 @@ +#include "./buffersearch.h" + +using namespace std; + +namespace CppUtilities { + +/*! + * \class BufferSearch + * \brief The BufferSearch struct invokes a callback if an initially given search term occurs in consecutively provided buffers. + * \remarks + * - The class works without making internal copies of the specified buffers, except for the search result. + * - The callback is invoked after the search term has been found and one of the specified termination characters occurred. The + * search result is passed to the callback. + * - The "search result" is the data after the last character of the search term and before any of the specified termination + * characters. + * - If no termination characters are specified, the callback is invoked directly after the search term occurred (with an empty + * search result). + * - If the specified give-up term has occurred, operator() will exit early and the specified callback will not be invoked + * anymore. + * - If the callback has been invoked, operator() will exit early and the callback will not be invoked anymore (even if the + * search term occurs again). Call reset() after consuming the result within the callback to continue the search. + */ + +/*! + * \brief Processes the specified \a buffer. Invokes the callback according to the remarks mentioned in the class documentation. + */ +void BufferSearch::operator()(const char *buffer, std::size_t bufferSize) +{ + if (m_hasResult || (!m_giveUpTerm.empty() && m_giveUpTermIterator == m_giveUpTerm.end())) { + return; + } + for (auto i = buffer, end = buffer + bufferSize; i != end; ++i) { + const auto currentChar = *i; + if (m_searchTermIterator == m_searchTerm.end()) { + if (m_terminationChars.empty()) { + m_hasResult = true; + } else { + for (const auto &terminationChar : m_terminationChars) { + if (currentChar == terminationChar) { + m_hasResult = true; + break; + } + } + } + if (m_hasResult) { + m_callback(*this, std::move(m_result)); + return; + } + m_result += currentChar; + continue; + } + if (currentChar == *m_searchTermIterator) { + ++m_searchTermIterator; + } else { + m_searchTermIterator = m_searchTerm.begin(); + } + if (m_giveUpTerm.empty()) { + continue; + } + if (currentChar == *m_giveUpTermIterator) { + ++m_giveUpTermIterator; + } else { + m_giveUpTermIterator = m_giveUpTerm.begin(); + } + } +} + +/*! + * \brief Resets the search to its initial state (assuming no characters of the search term or give-up term have been found yet). + */ +void BufferSearch::reset() +{ + m_searchTermIterator = m_searchTerm.begin(); + m_giveUpTermIterator = m_giveUpTerm.begin(); + m_terminationTermIterator = m_terminationTerm.begin(); + m_hasResult = false; + m_result.clear(); +} + +} // namespace CppUtilities diff --git a/io/buffersearch.h b/io/buffersearch.h new file mode 100644 index 0000000..d9c1e8f --- /dev/null +++ b/io/buffersearch.h @@ -0,0 +1,59 @@ +#ifndef IOUTILITIES_BUFFER_SEARCH_H +#define IOUTILITIES_BUFFER_SEARCH_H + +#include "../global.h" + +#include +#include +#include + +namespace CppUtilities { + +class CPP_UTILITIES_EXPORT BufferSearch { +public: + using CallbackType = std::function; + BufferSearch(std::string_view searchTerm, std::string_view terminationChars, std::string_view giveUpTerm, CallbackType &&callback); + void operator()(std::string_view buffer); + void operator()(const char *buffer, std::size_t bufferSize); + void reset(); + +private: + const std::string_view m_searchTerm; + const std::string_view m_terminationChars; + const std::string_view m_terminationTerm; + const std::string_view m_giveUpTerm; + const CallbackType m_callback; + std::string_view::const_iterator m_searchTermIterator; + std::string_view::const_iterator m_giveUpTermIterator; + std::string_view::const_iterator m_terminationTermIterator; + std::string m_result; + bool m_hasResult; +}; + +/*! + * \brief Constructs a new BufferSearch. Might be overloaded in the future. + */ +inline BufferSearch::BufferSearch( + std::string_view searchTerm, std::string_view terminationChars, std::string_view giveUpTerm, CallbackType &&callback) + : m_searchTerm(searchTerm) + , m_terminationChars(terminationChars) + , m_giveUpTerm(giveUpTerm) + , m_callback(std::move(callback)) + , m_searchTermIterator(m_searchTerm.begin()) + , m_giveUpTermIterator(m_giveUpTerm.begin()) + , m_terminationTermIterator(m_terminationTerm.begin()) + , m_hasResult(false) +{ +} + +/*! + * \brief Processes the specified \a buffer. Invokes the callback according to the remarks mentioned in the class documentation. + */ +inline void BufferSearch::operator()(std::string_view buffer) +{ + (*this)(buffer.data(), buffer.size()); +} + +} // namespace CppUtilities + +#endif // IOUTILITIES_BUFFER_SEARCH_H diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 11e8764..6102c66 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -7,6 +7,7 @@ #include "../io/binaryreader.h" #include "../io/binarywriter.h" #include "../io/bitreader.h" +#include "../io/buffersearch.h" #include "../io/copy.h" #include "../io/inifile.h" #include "../io/misc.h" @@ -43,6 +44,7 @@ class IoTests : public TestFixture { CPPUNIT_TEST(testBinaryReader); CPPUNIT_TEST(testBinaryWriter); CPPUNIT_TEST(testBitReader); + CPPUNIT_TEST(testBufferSearch); CPPUNIT_TEST(testPathUtilities); CPPUNIT_TEST(testIniFile); CPPUNIT_TEST(testAdvancedIniFile); @@ -62,6 +64,7 @@ public: void testBinaryReader(); void testBinaryWriter(); void testBitReader(); + void testBufferSearch(); void testPathUtilities(); void testIniFile(); void testAdvancedIniFile(); @@ -233,7 +236,7 @@ void IoTests::testBinaryWriter() } /*! - * \brief Tests the BitReader. + * \brief Tests the BitReader class. */ void IoTests::testBitReader() { @@ -259,6 +262,40 @@ void IoTests::testBitReader() CPPUNIT_ASSERT_EQUAL(static_cast(8 * sizeof(testData)), reader.bitsAvailable()); } +/*! + * \brief Tests the BufferSearch class. + */ +void IoTests::testBufferSearch() +{ + // setup search to test + auto expectedResult = std::string(); + auto hasResult = false; + auto bs = BufferSearch("Updated version: ", "\e\n", "Starting build", [&](BufferSearch &, std::string &&result) { + CPPUNIT_ASSERT_EQUAL(expectedResult, result); + CPPUNIT_ASSERT_MESSAGE("callback only invoked once", !hasResult); + hasResult = true; + }); + + // feed data into the search + char buffer[30]; + bs(buffer, 0); + CPPUNIT_ASSERT(!hasResult); + std::strcpy(buffer, "Starting Updated"); + bs(std::string_view(buffer, 16)); + CPPUNIT_ASSERT(!hasResult); + std::strcpy(buffer, " version: some "); + bs(buffer, 15); + CPPUNIT_ASSERT(!hasResult); + expectedResult = "some version number"; + std::strcpy(buffer, "version number\emore chars"); + bs(buffer, 25); + CPPUNIT_ASSERT(hasResult); + hasResult = false; + std::strcpy(buffer, "... Starting build ..."); + bs(buffer, 22); + CPPUNIT_ASSERT(!hasResult); +} + /*! * \brief Tests fileName() and removeInvalidChars(). */