From b07bc6b0ab31e54e7527b0a3baea8c8fcbe281dc Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 9 Oct 2020 19:48:15 +0100 Subject: [PATCH] Add FmtString class --- src/openrct2/localisation/Formatting.cpp | 128 +++++++++++++++++++++++ src/openrct2/localisation/Formatting.h | 42 ++++++++ test/tests/FormattingTests.cpp | 31 ++++++ 3 files changed, 201 insertions(+) diff --git a/src/openrct2/localisation/Formatting.cpp b/src/openrct2/localisation/Formatting.cpp index 695ecbf9ce..5903b8bcc3 100644 --- a/src/openrct2/localisation/Formatting.cpp +++ b/src/openrct2/localisation/Formatting.cpp @@ -19,6 +19,134 @@ namespace OpenRCT2 { + FmtString::token::token(FormatToken k, std::string_view s) + : kind(k) + , text(s) + { + } + + bool FmtString::token::IsLiteral() const + { + return kind == 0; + } + + FmtString::iterator::iterator(std::string_view s, size_t i) + : str(s) + , index(i) + { + update(); + } + + void FmtString::iterator::update() + { + auto i = index; + if (i >= str.size()) + { + current = token(); + return; + } + + if (str[i] == '{' && i + 1 < str.size() && str[i + 1] != '{') + { + // Move to end brace + do + { + i++; + } while (i < str.size() && str[i] != '}'); + if (i < str.size() && str[i] == '}') + i++; + } + else + { + do + { + i++; + } while (i < str.size() && str[i] != '{'); + } + current = CreateToken(i - index); + } + + bool FmtString::iterator::operator==(iterator& rhs) + { + return index == rhs.index; + } + + bool FmtString::iterator::operator!=(iterator& rhs) + { + return index != rhs.index; + } + + FmtString::token FmtString::iterator::CreateToken(size_t len) + { + std::string_view sztoken = str.substr(index, len); + if (sztoken.size() >= 2 && sztoken[0] == '{' && sztoken[1] != '{') + { + auto kind = format_get_code(sztoken.substr(1, len - 2)); + return token(kind, sztoken); + } + return token(0, sztoken); + } + + const FmtString::token* FmtString::iterator::operator->() const + { + return ¤t; + } + + const FmtString::token& FmtString::iterator::operator*() + { + return current; + } + + FmtString::iterator& FmtString::iterator::operator++() + { + if (index < str.size()) + { + index += current.text.size(); + update(); + } + return *this; + } + + FmtString::FmtString(std::string&& s) + { + _strOwned = std::move(s); + _str = _strOwned; + } + + FmtString::FmtString(std::string_view s) + : _str(s) + { + } + + FmtString::FmtString(const char* s) + : FmtString(std::string_view(s)) + { + } + + FmtString::iterator FmtString::begin() const + { + return iterator(_str, 0); + } + + FmtString::iterator FmtString::end() const + { + return iterator(_str, _str.size()); + } + + std::string FmtString::WithoutFormatTokens() const + { + std::string result; + result.reserve(_str.size() * 4); + for (const auto& t : *this) + { + if (t.IsLiteral()) + { + result += t.text; + } + } + return result; + } + char GetDigitSeperator() { return ','; diff --git a/src/openrct2/localisation/Formatting.h b/src/openrct2/localisation/Formatting.h index b9dca7a7fc..900904f22a 100644 --- a/src/openrct2/localisation/Formatting.h +++ b/src/openrct2/localisation/Formatting.h @@ -23,6 +23,48 @@ namespace OpenRCT2 { using FormatToken = uint32_t; + class FmtString + { + private: + std::string_view _str; + std::string _strOwned; + + public: + struct token + { + FormatToken kind{}; + std::string_view text; + + token() = default; + token(FormatToken k, std::string_view s); + bool IsLiteral() const; + }; + + struct iterator + { + std::string_view str; + size_t index; + token current; + + iterator(std::string_view s, size_t i); + void update(); + bool operator==(iterator& rhs); + bool operator!=(iterator& rhs); + token CreateToken(size_t len); + const token* operator->() const; + const token& operator*(); + iterator& operator++(); + }; + + FmtString(std::string&& s); + FmtString(std::string_view s); + FmtString(const char* s); + iterator begin() const; + iterator end() const; + + std::string WithoutFormatTokens() const; + }; + template void FormatArgument(std::stringstream& ss, FormatToken token, T arg); std::pair FormatNextPart(std::string_view& fmt); diff --git a/test/tests/FormattingTests.cpp b/test/tests/FormattingTests.cpp index 90651027c2..4ddbaf7e82 100644 --- a/test/tests/FormattingTests.cpp +++ b/test/tests/FormattingTests.cpp @@ -10,12 +10,42 @@ #include "openrct2/localisation/Formatting.h" #include +#include #include #include #include using namespace OpenRCT2; +class FmtStringTests : public testing::Test +{ +}; + +TEST_F(FmtStringTests, string_owned) +{ + auto fmt = FmtString(std::string("{BLACK}Guests: {INT32}")); + ASSERT_EQ("Guests: ", fmt.WithoutFormatTokens()); +} + +TEST_F(FmtStringTests, iteration) +{ + std::string actual; + + auto fmt = FmtString("{BLACK}Guests: {INT32}"); + for (const auto &t : fmt) + { + actual += String::StdFormat("[%d:%s]", t.kind, std::string(t.text).c_str()); + } + + ASSERT_EQ("[142:{BLACK}][0:Guests: ][124:{INT32}]", actual); +} + +TEST_F(FmtStringTests, without_format_tokens) +{ + auto fmt = FmtString("{BLACK}Guests: {INT32}"); + ASSERT_EQ("Guests: ", fmt.WithoutFormatTokens()); +} + class FormattingTests : public testing::Test { private: @@ -48,6 +78,7 @@ protected: }; std::shared_ptr FormattingTests::_context; + TEST_F(FormattingTests, no_args) { auto actual = FormatString("test string");