Toponaming/Base: Add ASCII stream output class (#13209)
* Toponaming/Base: Add ASCII stream output class * Remove the ref from std::string * Update based on review comments
This commit is contained in:
@@ -256,6 +256,97 @@ private:
|
||||
std::ostringstream _ss;
|
||||
};
|
||||
|
||||
/**
|
||||
* The TextOutputStream class provides writing of ASCII data to an ostream, with custom handling
|
||||
* for std::string to make it easier to write a single multi-line (or multi-word) string. This is
|
||||
* designed for easy compatibility with the LinkStage3 implementation of the OutputStream class,
|
||||
* used to store StringHashers for the toponaming mitigation technique.
|
||||
*/
|
||||
class BaseExport TextOutputStream: public Stream
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
* @param rout: upstream output
|
||||
*/
|
||||
explicit TextOutputStream(std::ostream& rout)
|
||||
: _out(rout)
|
||||
{}
|
||||
|
||||
TextOutputStream(const TextOutputStream&) = delete;
|
||||
|
||||
TextOutputStream(const TextOutputStream&&) noexcept = delete;
|
||||
|
||||
void operator=(const TextOutputStream&) = delete;
|
||||
|
||||
void operator=(const TextOutputStream&&) = delete;
|
||||
|
||||
~TextOutputStream() override = default;
|
||||
|
||||
template<typename T>
|
||||
TextOutputStream& operator<<(T object)
|
||||
{
|
||||
_out << object << '\n';
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextOutputStream& operator<<(const char* object)
|
||||
{
|
||||
std::string_view str(object);
|
||||
|
||||
// Count the lines so that we can deal with potential EOL conversions by external software
|
||||
uint32_t lineCount = 0;
|
||||
for (const auto character : str) {
|
||||
if (character == '\n') {
|
||||
++lineCount;
|
||||
}
|
||||
}
|
||||
// Stores the line count followed by a colon as the delimiter. We don't use
|
||||
// whitespace because the input stream may also start with whitespace.
|
||||
_out << lineCount << ':';
|
||||
|
||||
// Store the text, and normalize the end of line to a single '\n'.
|
||||
bool foundSlashR = false;
|
||||
for (const auto character : str) {
|
||||
if (character == '\r') {
|
||||
foundSlashR = true;
|
||||
continue;
|
||||
}
|
||||
if (foundSlashR) {
|
||||
if (character != '\n') {
|
||||
// We allow '\r' if it is not at the end of a line
|
||||
_out.put('\r');
|
||||
}
|
||||
foundSlashR = false; // Reset for next time
|
||||
}
|
||||
_out.put(character);
|
||||
}
|
||||
|
||||
// Add an extra newline as the delimiter for the following data.
|
||||
_out.put('\n');
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextOutputStream& operator<<(const std::string& object)
|
||||
{
|
||||
return (*this) << object.c_str();
|
||||
}
|
||||
|
||||
TextOutputStream& operator<<(char object)
|
||||
{
|
||||
_out.put(object);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
// test if _Ipfx succeeded
|
||||
return !_out.eof();
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& _out;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ target_sources(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Quantity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Reader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Rotation.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Stream.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TimeInfo.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Tools.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Tools2D.cpp
|
||||
|
||||
127
tests/src/Base/Stream.cpp
Normal file
127
tests/src/Base/Stream.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#include "Base/Stream.h"
|
||||
|
||||
|
||||
class TextOutputStreamTest: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{}
|
||||
|
||||
void TearDown() override
|
||||
{}
|
||||
};
|
||||
|
||||
TEST_F(TextOutputStreamTest, singleLineCharStar)
|
||||
{
|
||||
// Arrange
|
||||
const std::string testString("Single line const char *");
|
||||
std::ostringstream ss;
|
||||
Base::TextOutputStream tos(ss);
|
||||
|
||||
// Act
|
||||
tos << testString;
|
||||
|
||||
// Assert - the number of newlines in the string, a colon, the string, a newline
|
||||
EXPECT_EQ(std::string("0:") + testString + "\n", ss.str());
|
||||
}
|
||||
|
||||
TEST_F(TextOutputStreamTest, multiLineCharStar)
|
||||
{
|
||||
// Arrange
|
||||
const std::string testString("Multi-line\nconst char *");
|
||||
std::ostringstream ss;
|
||||
Base::TextOutputStream tos(ss);
|
||||
|
||||
// Act
|
||||
tos << testString; // Testing it with a string instead of a const char * -- failing right now
|
||||
|
||||
// Assert - the number of newlines in the string, a colon, the string, a newline
|
||||
EXPECT_EQ(std::string("1:") + testString + "\n", ss.str());
|
||||
}
|
||||
|
||||
TEST_F(TextOutputStreamTest, singleLineCharStarWithCarriageReturns)
|
||||
{
|
||||
// Arrange
|
||||
const std::string testString("Single-line\rconst char *");
|
||||
std::ostringstream ss;
|
||||
Base::TextOutputStream tos(ss);
|
||||
|
||||
// Act
|
||||
tos << testString;
|
||||
|
||||
// Assert - the number of newlines in the string, a colon, the string, a newline. Carriage
|
||||
// returns are left alone because they aren't followed by a newline
|
||||
EXPECT_EQ(std::string("0:") + testString + "\n", ss.str());
|
||||
}
|
||||
|
||||
TEST_F(TextOutputStreamTest, multiLineCharStarWithCarriageReturnsAndNewlines)
|
||||
{
|
||||
// Arrange
|
||||
const std::string testString("Multi-line\r\nconst char *");
|
||||
const std::string testStringWithoutCR("Multi-line\nconst char *");
|
||||
std::ostringstream ss;
|
||||
Base::TextOutputStream tos(ss);
|
||||
|
||||
// Act
|
||||
tos << testString;
|
||||
|
||||
// Assert - the number of newlines in the string, a colon, the string, a newline, but the string
|
||||
// has been stripped of the carriage returns.
|
||||
EXPECT_EQ(std::string("1:") + testStringWithoutCR + "\n", ss.str());
|
||||
}
|
||||
|
||||
|
||||
class TextStreamIntegrationTest: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{}
|
||||
|
||||
void TearDown() override
|
||||
{}
|
||||
};
|
||||
|
||||
TEST_F(TextStreamIntegrationTest, OutputThenInputSimpleMultiLine)
|
||||
{
|
||||
// Arrange
|
||||
std::string multiLineString("One\nTwo\nThree");
|
||||
|
||||
// Act
|
||||
std::ostringstream ssO;
|
||||
Base::TextOutputStream tos(ssO);
|
||||
tos << multiLineString;
|
||||
std::istringstream ssI(ssO.str());
|
||||
Base::TextInputStream tis(ssI);
|
||||
std::string result;
|
||||
tis >> result;
|
||||
|
||||
// Assert
|
||||
EXPECT_EQ(multiLineString, result);
|
||||
}
|
||||
|
||||
TEST_F(TextStreamIntegrationTest, OutputThenInputMultiLineWithCarriageReturns)
|
||||
{
|
||||
// Arrange
|
||||
std::string multiLineString("One\r\nTwo\r\nThree");
|
||||
std::string multiLineStringResult("One\nTwo\nThree");
|
||||
|
||||
// Act
|
||||
std::ostringstream ssO;
|
||||
Base::TextOutputStream tos(ssO);
|
||||
tos << multiLineString;
|
||||
std::istringstream ssI(ssO.str());
|
||||
Base::TextInputStream tis(ssI);
|
||||
std::string result;
|
||||
tis >> result;
|
||||
|
||||
// Assert
|
||||
EXPECT_EQ(multiLineStringResult, result);
|
||||
}
|
||||
Reference in New Issue
Block a user