mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-23 07:43:01 +01:00
Add FmtString class
This commit is contained in:
@@ -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 ',';
|
||||
|
||||
@@ -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<typename T> void FormatArgument(std::stringstream& ss, FormatToken token, T arg);
|
||||
|
||||
std::pair<std::string_view, uint32_t> FormatNextPart(std::string_view& fmt);
|
||||
|
||||
@@ -10,12 +10,42 @@
|
||||
#include "openrct2/localisation/Formatting.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <openrct2/core/String.hpp>
|
||||
#include <openrct2/Context.h>
|
||||
#include <openrct2/OpenRCT2.h>
|
||||
#include <openrct2/config/Config.h>
|
||||
|
||||
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<IContext> FormattingTests::_context;
|
||||
|
||||
TEST_F(FormattingTests, no_args)
|
||||
{
|
||||
auto actual = FormatString("test string");
|
||||
|
||||
Reference in New Issue
Block a user